[
  {
    "path": ".coveragerc",
    "content": "[run]\nbranch = True\nomit =\n    /opt/python/*\n\n[report]\nexclude_lines =\n    pragma: no cover\n    raise NotImplementedError\n"
  },
  {
    "path": ".flake8",
    "content": "[flake8]\nmax-line-length=120\nignore =\n    E731\n    W504\n\nexclude =\n    build\n    .tox\n\nper-file-ignores =\n    docs/conf.py:E265,E401,E402\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve this library.\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Environment**\n - OS, including version\n - Version of this library\n - Version of GnuPG\n\n**Additional information**\nAdd any other information about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/python-package.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches: [ master ]\n    paths-ignore:\n      - 'LICENSE.*'\n      - 'README.*'\n      - '.github/ISSUE-TEMPLATE/**'\n      - 'docs/**'\n      - '.hgignore'\n      - '.gitignore'\n\n  pull_request:\n    branches: [ master ]\n    paths-ignore:\n      - 'LICENSE.*'\n      - 'README.*'\n      - '.github/ISSUE-TEMPLATE/**'\n      - 'docs/**'\n      - '.hgignore'\n      - '.gitignore'\n\n  schedule:  # at 03:07 on day-of-month 7\n    - cron: '7 3 7 * *'\n\nenv:\n  FORCE_COLOR: 1\n  PIP_DISABLE_PIP_VERSION_CHECK: 1\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.13t', '3.14', '3.14t', 'pypy-3.9']\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install Windows-only dependencies\n      run: |\n        $env:PATH = \"C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\ProgramData\\chocolatey\\bin\"\n        [Environment]::SetEnvironmentVariable(\"Path\", $env:PATH, \"Machine\")\n        choco install gnupg --version \"2.4.8\"\n        echo \"C:\\Program Files (x86)\\GnuPG\\bin\" >> $env:GITHUB_PATH\n      if: ${{ matrix.os == 'windows-latest' }}\n    - name: Test with unittest\n      run: |\n        gpg --version\n        python test_gnupg.py -v\n      env:\n        NO_EXTERNAL_TESTS: 1\n    - name: Report failure info\n      if: ${{ failure() }}\n      run: |\n        cat test_gnupg.log\n    - name: Test with coverage\n      run: |\n        pip install coverage\n        coverage run --branch test_gnupg.py\n        coverage xml\n      env:\n        NO_EXTERNAL_TESTS: 1\n    - name: Upload coverage to Codecov\n      uses: codecov/codecov-action@v5\n      with:\n        flags: unittests\n        files: coverage.xml\n"
  },
  {
    "path": ".gitignore",
    "content": "build\ndist\nkeys\n.tox\n.egg-info\nDETAILS\nMANIFEST\nrandom_binary_data\n__pycache__\n*.pyc\n*.log\n.dict-validwords\ndocs/themes/\ndocs/_build/\n.idea/\n.idea_modules/\n*.iml\n*.iws\n*.ipr\n.vscode/\n.settings/\n.venv/\nvenv/\n\n"
  },
  {
    "path": ".hgignore",
    "content": "(build|dist|keys|\\.(tox|egg-info))/\n(DETAILS|MANIFEST|random_binary_data|watcher.conf|hover.json)$\n\\.(pyc|log|dict-validwords|yapf)$\nlocal.*\\.(sh|cmd)\ndocs/themes/\n"
  },
  {
    "path": ".hgtags",
    "content": "afbe4e74cfc7fb79dde344499e8934c88874ef85 0.3.6\n1979c07150aa6c7cb16b7f70db71f1b973153b93 0.3.7\n7a54f558eb05e2b0ff25b51f430255d92312705c 0.3.8\n1ab8db449e5b2b28b2d1f4c96677dd31050d296e 0.3.9\nd18b8320539fc43cf406e829080d9fb72388f7ce 0.4.0\n1fe9f4c3d9b3a7672a43beb8ee08ac3e8234710d 0.4.1\n20a9a5727c11ea07188f99377c67d1bd937f4d7e 0.4.2\ne0f2692d6539aca706b63dba22d900d2c70d59f8 0.4.3\nc2dc6e154027ab8fc13eba8a440ea43568c37d8b 0.4.4\n5cedc567072cead1415b24b7aa1ee74901c3f66f 0.4.4.1\n79af87708a2338d1ec58b5ff32747a6dc32e5147 0.4.5\n5eae1f2c1034f1eb3e5b29dece3a7006cd687733 0.4.6\n2eae4d96f406b9f09b274b6a2457b0a87a1a894e 0.4.7\n0a57c41eb34b01878fa6ac00a6f1afb2e1c4f6be 0.4.8\n9e58092577ebf033bd8b95dc50905e518ff4a7ea 0.4.9\n129c8fa7451a75c71f5e2fd54686896b266fefc8 0.5.0\n1f1265fd99f1dda764dfa5ba3b4f34093203eaf5 0.5.1\nf7d1effbb6e19cc233a139510c04f0f3aad83dd7 0.5.2\nb0327e6c3ce295d2f5780af98e7aec0cf1862091 0.5.3\n3efac76918850def676c20aa4bddb6e132adf1b6 0.5.4\n1b77f5b12ad72096eb71fd94b2f767ffc565d3eb 0.5.5\n2825bcb5914434854a11657563d829cfd9d06b67 0.5.6\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# We recommend specifying your dependencies to enable reproducible builds:\n# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\npython:\n  install:\n  - requirements: docs/requirements.txt\n\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2008-2022 by Vinay Sajip.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright notice,\n      this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright notice,\n      this list of conditions and the following disclaimer in the documentation\n      and/or other materials provided with the distribution.\n    * The name(s) of the copyright holder(s) may not be used to endorse or\n      promote products derived from this software without specific prior\n      written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) \"AS IS\" AND ANY EXPRESS OR\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO\nEVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\nOR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE.txt\ninclude README.rst\ninclude test_gnupg.py\ninclude messages.json\ninclude test_*ring.gpg\n\n"
  },
  {
    "path": "README.rst",
    "content": "|badge1| |badge2| |badge3|\n\n.. |badge1| image:: https://img.shields.io/github/actions/workflow/status/vsajip/python-gnupg/python-package.yml\n   :alt: GitHub test status\n\n.. |badge2| image:: https://img.shields.io/codecov/c/github/vsajip/python-gnupg\n   :target: https://app.codecov.io/gh/vsajip/python-gnupg\n   :alt: GitHub coverage status\n\n.. |badge3| image:: https://img.shields.io/pypi/v/python-gnupg\n   :target: https://pypi.org/project/python-gnupg/\n   :alt: PyPI package\n\n\nWhat is it?\n===========\n\nThe GNU Privacy Guard (gpg, or gpg.exe on Windows) is a command-line program\nwhich provides support for programmatic access via spawning a separate process\nto run it and then communicating with that process from your program.\n\nThis project, ``python-gnupg``, implements a Python library which takes care\nof the internal details and allows its users to generate and manage keys,\nencrypt and decrypt data, and sign and verify messages.\n\nInstallation\n============\n\nInstalling from PyPI\n--------------------\n\nYou can install this package from the Python Package Index (pyPI) by running::\n\n    pip install python-gnupg\n\n.. important::\n   There is at least one fork of this project, which was apparently created\n   because an earlier version of this software used the ``subprocess`` module\n   with ``shell=True``, making it vulnerable to shell injection. **This is no\n   longer the case**.\n\n   Forks may not be drop-in compatible with this software, so take care to use\n   the correct version, as indicated in the ``pip install`` command above.\n\n\nInstalling from a source distribution archive\n---------------------------------------------\nTo install this package from a source distribution archive, do the following:\n\n1. Extract all the files in the distribution archive to some directory on your\n   system.\n2. In that directory, run ``pip install .``, referencing a suitable ``pip`` (e.g. one\n   from a specific venv which you want to install to).\n3. Optionally, run ``python test_gnupg.py`` to ensure that the package is\n   working as expected.\n\nCredits\n=======\n\n* The developers of the GNU Privacy Guard.\n* The original version of this module was developed by Andrew Kuchling.\n* It was improved by Richard Jones.\n* It was further improved by Steve Traugott.\n\nThe present incarnation, based on the earlier versions, uses the ``subprocess``\nmodule and so works on Windows as well as Unix/Linux platforms. It's not,\nhowever, 100% backwards-compatible with earlier incarnations.\n\nChange log\n==========\n\n.. note:: GCnn refers to an issue nn on Google Code.\n\n\n0.5.7 (future)\n--------------\n\nReleased: Not yet\n\n0.5.6\n-----\n\nReleased: 2025-12-31\n\n* Fix #261: Ensure capability, fingerprint and keygrip are added to subkey_info.\n\n* Set username in the result when Verify uses a signing key that has expired or been\n  revoked. Thanks to Steven Galgano for the patch.\n\n0.5.5\n-----\n\nReleased: 2025-08-04\n\n* Fix #249: Handle fetching GPG version when not the first item in the configuration.\n\n* Fix #250: Capture uid info in a uid_map attribute of ScanKeys/ListKeys.\n\n* Fix #255: Improve handling of exceptions raised in background threads.\n\n\n0.5.4\n-----\n\nReleased: 2025-01-07\n\n* Fix #242: Handle exceptions in ``on_data`` callable.\n\n\n0.5.3\n-----\n\nReleased: 2024-09-20\n\n* Fix #117: Add WKD (Web Key Directory) support for auto-locating keys. Thanks to Myzel394\n  for the patch.\n\n* Fix #237: Ensure local variable is initialized even when an exception occurs.\n\n* Fix #239: Remove logging of decryption result.\n\n\n0.5.2\n-----\n\nReleased: 2023-12-12\n\n* Fix #228: Clarify documentation for encryption/decryption.\n\n* Make I/O buffer size configurable via ``buffer_size`` attribute on a ``GPG`` instance.\n\n\n0.5.1\n-----\n\nReleased: 2023-07-22\n\n* Added ``TRUST_EXPIRED`` to ``trust_keys``. Thanks to Leif Liddy for the patch.\n\n* Fix #206: Remove deprecated ``--always-trust`` in favour of ``--trust-model always``\n\n* Fix #208: Add ``status_detail`` attribute to result objects which is populated when\n  the status is ``'invalid recipient'`` (encryption/decryption) or ``'invalid signer'``\n  (signing). This attribute will be set when the result object's ``status`` attribute is\n  set to ``invalid recipient`` and will contain more information about the failure in the\n  form of ``reason:ident`` where ``reason`` is a text description of the reason, and\n  ``ident`` identifies the recipient key.\n\n* Add ``scan_keys_mem()`` function to scan keys in a string. Thanks to Sky Moore\n  for the patch.\n\n* Fix #214: Handle multiple signatures when one of them is invalid or unverified.\n\n* A ``problems`` attribute was added which holds problems reported by ``gpg``\n  during verification. This is a list of dictionaries, one for each reported\n  problem. Each dictionary will have ``status`` and ``keyid`` keys indicating\n  the problem and the corresponding key; other information in the dictionaries\n  will be error specific.\n\n* Fix #217: Use machine-readable interface to query the ``gpg`` version. Thanks to Justus\n  Winter for the patch.\n\n* Added the ability to export keys to a file. Thanks to Leif Liddy for the patch.\n\n\n0.5.0\n-----\n\nReleased: 2022-08-23\n\n* Fixed #181: Added the ability to pass file paths to encrypt_file, decrypt_file,\n  sign_file, verify_file, get_recipients_file and added import_keys_file.\n\n* Fixed #183: Handle FAILURE and UNEXPECTED conditions correctly. Thanks to sebbASF for\n  the patch.\n\n* Fixed #185: Handle VALIDSIG arguments more robustly.\n\n* Fixed #188: Remove handling of DECRYPTION_FAILED from Verify code, as not required\n  there. Thanks to sebbASF for the patch.\n\n* Fixed #190: Handle KEY_CREATED more robustly.\n\n* Fixed #191: Handle NODATA messages during verification.\n\n* Fixed #196: Don't log chunk data by default, as it could contain sensitive\n  information (during decryption, for example).\n\n* Added the ability to pass an environment to the gpg executable. Thanks to Edvard\n  Rejthar for the patch.\n\n\n0.4.9\n-----\n\nReleased: 2022-05-20\n\n* Fixed #161: Added a status attribute to the returned object from gen_key() which\n  is set to 'ok' if a key was successfully created, or 'key not created' if that\n  was reported by gpg, or None in any other case.\n\n* Fixed #164: Provided the ability to add subkeys. Thanks to Daniel Kilimnik for the\n  feature request and patch.\n\n* Fixed #166: Added keygrip values to the information collected when keys are listed.\n  Thanks to Daniel Kilimnik for the feature request and patch.\n\n* Fixed #173: Added extra_args to send_keys(), recv_keys() and search_keys() to allow\n  passing options relating to key servers.\n\n0.4.8\n-----\n\nReleased: 2021-11-24\n\n* Fixed #147: Return gpg's return code in all result instances.\n\n* Fixed #152: Add check for invalid file objects.\n\n* Fixed #157: Provide more useful status message when a secret key is absent.\n\n* Fixed #158: Added a get_recipients() API to find the recipients of an encrypted\n  message without decrypting it.\n\n\n0.4.7\n-----\n\nReleased: 2021-03-11\n\n* Fixed #129, #141: Added support for no passphrase during key generation.\n\n* Fixed #143: Improved permission-denied test. Thanks to Elliot Cameron for the patch.\n\n* Fixed #144: Updated logging to only show partial results.\n\n* Fixed #146: Allowed a passphrase to be passed to import_keys(). Thanks to Chris de\n  Graaf for the patch.\n\n\n0.4.6\n-----\n\nReleased: 2020-04-17\n\n* Fixed #122: Updated documentation about gnupghome needing to be an existing\n  directory.\n\n* Fixed #123: Handled error conditions from gpg when calling trust_keys().\n\n* Fixed #124: Avoided an exception being raised when ImportResult.summary()\n  was called after a failed recv_keys().\n\n* Fixed #128: Added ECC support by changing key generation parameters. (The Key-Length\n  value isn't added if a curve is specified.)\n\n* Fixed #130: Provided a mechanism to provide more complete error messages.\n\nSupport for Python versions 3.5 and under is discontinued, except for Python 2.7.\n\n\n0.4.5\n-----\n\nReleased: 2019-08-12\n\n* Fixed #107: Improved documentation.\n\n* Fixed #112: Raised a ValueError if a gnupghome is specified which is not an\n  existing directory.\n\n* Fixed #113: Corrected stale link in the documentation.\n\n* Fixed #116: Updated documentation to clarify when spurious key-expired/\n  signature-expired messages might be seen.\n\n* Fixed #119: Added --yes to avoid pinentry when deleting secret keys with\n  GnuPG >= 2.1.\n\n* A warning is logged if gpg returns a non-zero return code.\n\n* Added ``extra_args`` to ``import_keys``.\n\n* Added support for CI using AppVeyor.\n\n\n0.4.4\n-----\n\nReleased: 2019-01-24\n\n* Fixed #108: Changed how any return value from the ``on_data`` callable is\n  processed. In earlier versions, the return value was ignored. In this version,\n  if the return value is ``False``, the data received from ``gpg`` is not\n  buffered. Otherwise (if the value is ``None`` or ``True``, for example), the\n  data is buffered as normal. This functionality can be used to do your own\n  buffering, or to prevent buffering altogether.\n\n  The ``on_data`` callable is also called once with an empty byte-string to\n  signal the end of data from ``gpg``.\n\n* Fixed #97: Added an additional attribute ``check_fingerprint_collisions`` to\n  ``GPG`` instances, which defaults to ``False``. It seems that ``gpg`` is happy\n  to have duplicate keys and fingerprints in a keyring, so we can't be too\n  strict. A user can set this attribute of an instance to ``True`` to trigger a\n  check for collisions.\n\n* Fixed #111: With GnuPG 2.2.7 or later, provide the fingerprint of a signing\n  key for a failed signature verification, if available.\n\n* Fixed #21: For verification where multiple signatures are involved, a\n  mapping of signature_ids to fingerprint, keyid, username, creation date,\n  creation timestamp and expiry timestamp is provided.\n\n* Added a check to disallow certain control characters ('\\r', '\\n', NUL) in\n  passphrases.\n\n\n0.4.3\n-----\n\nReleased: 2018-06-13\n\n* Added --no-verbose to the gpg command line, in case verbose is specified in\n  gpg.conf - we don't need verbose output.\n\n\n0.4.2\n-----\n\nReleased: 2018-03-28\n\n* Fixed #81: Subkey information is now collected and returned in a ``subkey_info``\n  dictionary keyed by the subkey's ID.\n\n* Fixed #84: GPG2 version is now correctly detected on OS X.\n\n* Fixed #94: Added ``expect_passphrase`` password for use on GnuPG >= 2.1 when\n  passing passphrase to ``gpg`` via pinentry.\n\n* Fixed #95: Provided a ``trust_keys`` method to allow setting the trust level\n  for keys. Thanks to William Foster for a suggested implementation.\n\n* Made the exception message when the gpg executable is not found contain the\n  path of the executable that was tried. Thanks to Kostis Anagnostopoulos for\n  the suggestion.\n\n* Fixed #100: Made the error message less categorical in the case of a failure\n  with an unspecified reason, adding some information from gpg error codes when\n  available.\n\n\n0.4.1\n-----\n\nReleased: 2017-07-06\n\n* Updated message handling logic to no longer raise exceptions when a message\n  isn't recognised. Thanks to Daniel Kahn Gillmor for the patch.\n\n* Always use always use ``--fixed-list-mode``, ``--batch`` and\n  ``--with-colons``. Thanks to Daniel Kahn Gillmor for the patch.\n\n* Improved ``scan_keys()`` handling on GnuPG >= 2.1. Thanks to Daniel Kahn\n  Gillmor for the patch.\n\n* Improved test behaviour with GnuPG >= 2.1. Failures when deleting test\n  directory trees are now ignored. Thanks to Daniel Kahn Gillmor for the patch.\n\n* Added ``close_file`` keyword argument to verify_file to allow the file closing\n  to be made optional. Current behaviour is maintained - ``close_file=False``\n  can be passed to skip closing the file being verified.\n\n* Added the ``extra_args`` keyword parameter to allow custom arguments to be\n  passed to the ``gpg`` executable.\n\n* Instances of the ``GPG`` class now have an additional ``on_data`` attribute,\n  which defaults to ``None``. It can be set to a callable which will be called\n  with a single argument - a binary chunk of data received from the ``gpg``\n  executable. The callable can do whatever it likes with the chunks passed to it\n  - e.g. write them to a separate stream. The callable should not raise any\n  exceptions (unless it wants the current operation to fail).\n\n\n0.4.0\n-----\n\nReleased: 2017-01-29\n\n* Added support for ``KEY_CONSIDERED`` in more places - encryption /\n  decryption, signing, key generation and key import.\n\n* Partial fix for #32 (GPG 2.1 compatibility). Unfortunately, better\n  support cannot be provided at this point, unless there are certain\n  changes (relating to pinentry popups) in how GPG 2.1 works.\n\n* Fixed #60: An IndexError was being thrown by ``scan_keys()``.\n\n* Ensured that utf-8 encoding is used when the ``--with-column`` mode is\n  used. Thanks to Yann Leboulanger for the patch.\n\n* ``list_keys()`` now uses ``--fixed-list-mode``. Thanks to Werner Koch\n  for the pointer.\n\n\n0.3.9\n-----\n\nReleased: 2016-09-10\n\n* Fixed #38: You can now request information about signatures against\n  keys. Thanks to SunDwarf for the suggestion and patch, which was used\n  as a basis for this change.\n\n* Fixed #49: When exporting keys, no attempt is made to decode the output when\n  armor=False is specified.\n\n* Fixed #53: A ``FAILURE`` message caused by passing an incorrect passphrase\n  is handled.\n\n* Handled ``EXPORTED`` and ``EXPORT_RES`` messages while exporting keys. Thanks\n  to Marcel Pörner for the patch.\n\n* Fixed #54: Improved error message shown when gpg is not available.\n\n* Fixed #55: Added support for ``KEY_CONSIDERED`` while verifying.\n\n* Avoided encoding problems with filenames under Windows. Thanks to Kévin\n  Bernard-Allies for the patch.\n\n* Fixed #57: Used a better mechanism for comparing keys.\n\n\n0.3.8\n-----\n\nReleased: 2015-09-24\n\n* Fixed #22: handled ``PROGRESS`` messages during verification and signing.\n\n* Fixed #26: handled ``PINENTRY_LAUNCHED`` messages during verification,\n  decryption and key generation.\n\n* Fixed #28: Allowed a default Name-Email to be computed even when neither of\n  ``LOGNAME`` and ``USERNAME`` are in the environment.\n\n* Fixed #29: Included test files missing from the tarball in previous versions.\n\n* Fixed #39: On Python 3.x, passing a text instead of a binary stream caused\n  file decryption to hang due to a ``UnicodeDecodeError``. This has now been\n  correctly handled: The decryption fails with a \"no data\" status.\n\n* Fixed #41: Handled Unicode filenames correctly by encoding them on 2.x using\n  the file system encoding.\n\n* Fixed #43: handled ``PINENTRY_LAUNCHED`` messages during key export. Thanks\n  to Ian Denhardt for looking into this.\n\n* Hide the console window which appears on Windows when gpg is spawned.\n  Thanks to Kévin Bernard-Allies for the patch.\n\n* Subkey fingerprints are now captured.\n\n* The returned value from the ``list_keys`` method now has a new attribute,\n  ``key_map``, which is a dictionary mapping key and subkey fingerprints to\n  the corresponding key's dictionary. With this change, you don't need to\n  iterate over the (potentially large) returned list to search for a key with\n  a given fingerprint - the ``key_map`` dict will take you straight to the key\n  info, whether the fingerprint you have is for a key or a subkey. Thanks to\n  Nick Daly for the initial suggestion.\n\n0.3.7\n-----\n\nReleased: 2014-12-07\n\nSigned with PGP key: Vinay Sajip (CODE SIGNING KEY) <vinay_sajip@yahoo.co.uk>\n\nKey Fingerprint    : CA74 9061 914E AC13 8E66 EADB 9147 B477 339A 9B86\n\n* Added an ``output`` keyword parameter to the ``sign`` and\n  ``sign_file`` methods, to allow writing the signature to a file.\n  Thanks to Jannis Leidel for the patch.\n\n* Allowed specifying ``True`` for the ``sign`` keyword parameter,\n  which allows use of the default key for signing and avoids having to\n  specify a key id when it's desired to use the default. Thanks to\n  Fabian Beutel for the patch.\n\n* Used a uniform approach with subprocess on Windows and POSIX: shell=True\n  is not used on either.\n\n* When signing/verifying, the status is updated to reflect any expired or\n  revoked keys or signatures.\n\n* Handled 'NOTATION_NAME' and 'NOTATION_DATA' during verification.\n\n* Fixed #1, #16, #18, #20: Quoting approach changed, since now shell=False.\n\n* Fixed #14: Handled 'NEED_PASSPHRASE_PIN' message.\n\n* Fixed #8: Added a scan_keys method to allow scanning of keys without the\n  need to import into a keyring. Thanks to Venzen Khaosan for the suggestion.\n\n* Fixed #5: Added '0x' prefix when searching for keys. Thanks to Aaron Toponce\n  for the report.\n\n* Fixed #4: Handled 'PROGRESS' message during encryption. Thanks to Daniel\n  Mills for the report.\n\n* Fixed #3: Changed default encoding to Latin-1.\n\n* Fixed #2: Raised ValueError if no recipients were specified\n  for an asymmetric encryption request.\n\n* Handled 'UNEXPECTED' message during verification. Thanks to\n  David Andersen for the patch.\n\n* Replaced old range(len(X)) idiom with enumerate().\n\n* Refactored ``ListKeys`` / ``SearchKeys`` classes to maximise use of common\n  functions.\n\n* Fixed GC94: Added ``export-minimal`` and ``armor`` options when exporting\n  keys. This addition was inadvertently left out of 0.3.6.\n\n0.3.6\n-----\n\nReleased: 2014-02-05\n\n* Fixed GC82: Enabled fast random tests on gpg as well as gpg2.\n* Fixed GC85: Avoided deleting temporary file to preserve its permissions.\n* Fixed GC87: Avoided writing passphrase to log.\n* Fixed GC95: Added ``verify_data()`` method to allow verification of\n  signatures in memory.\n* Fixed GC96: Regularised end-of-line characters.\n* Fixed GC98: Rectified problems with earlier fix for shell injection.\n\n0.3.5\n-----\n\nReleased: 2013-08-30\n\n* Added improved shell quoting to guard against shell injection.\n* Fixed GC76: Added ``search_keys()`` and ``send_keys()`` methods.\n* Fixed GC77: Allowed specifying a symmetric cipher algorithm.\n* Fixed GC78: Fell back to utf-8 encoding when no other could be determined.\n* Fixed GC79: Default key length is now 2048 bits.\n* Fixed GC80: Removed the Name-Comment default in key generation.\n\n0.3.4\n-----\n\nReleased: 2013-06-05\n\n* Fixed GC65: Fixed encoding exception when getting version.\n* Fixed GC66: Now accepts sets and frozensets where appropriate.\n* Fixed GC67: Hash algorithm now captured in sign result.\n* Fixed GC68: Added support for ``--secret-keyring``.\n* Fixed GC70: Added support for multiple keyrings.\n\n0.3.3\n-----\n\nReleased: 2013-03-11\n\n* Fixed GC57: Handled control characters in ``list_keys()``.\n* Fixed GC61: Enabled fast random for testing.\n* Fixed GC62: Handled ``KEYEXPIRED`` status.\n* Fixed GC63: Handled ``NO_SGNR`` status.\n\n0.3.2\n-----\n\nReleased: 2013-01-17\n\n* Fixed GC56: Disallowed blank values in key generation.\n* Fixed GC57: Handled colons and other characters in ``list_keys()``.\n* Fixed GC59/GC60: Handled ``INV_SGNR`` status during verification and removed\n  calls requiring interactive password input from doctests.\n\n0.3.1\n-----\n\nReleased: 2012-09-01\n\n* Fixed GC45: Allowed additional arguments to gpg executable.\n* Fixed GC50: Used latin-1 encoding in tests when it's known to be required.\n* Fixed GC51: Test now returns non-zero exit status on test failure.\n* Fixed GC53: Now handles ``INV_SGNR`` and ``KEY_NOT_CREATED`` statuses.\n* Fixed GC55: Verification and decryption now return trust level of signer in\n  integer and text form.\n\n0.3.0\n-----\n\nReleased: 2012-05-12\n\n* Fixed GC49: Reinstated Yann Leboulanger's change to support subkeys\n  (accidentally left out in 0.2.7).\n\n0.2.9\n-----\n\nReleased: 2012-03-29\n\n* Fixed GC36: Now handles ``CARDCTRL`` and ``POLICY_URL`` messages.\n* Fixed GC40: Now handles ``DECRYPTION_INFO``, ``DECRYPTION_FAILED`` and\n  ``DECRYPTION_OKAY`` messages.\n* The ``random_binary_data file`` is no longer shipped, but constructed by the\n  test suite if needed.\n\n0.2.8\n-----\n\nReleased: 2011-09-02\n\n* Fixed GC29: Now handles ``IMPORT_RES`` while verifying.\n* Fixed GC30: Fixed an encoding problem.\n* Fixed GC33: Quoted arguments for added safety.\n\n0.2.7\n-----\n\nReleased: 2011-04-10\n\n* Fixed GC24: License is clarified as BSD.\n* Fixed GC25: Incorporated Daniel Folkinshteyn's changes.\n* Fixed GC26: Incorporated Yann Leboulanger's subkey change.\n* Fixed GC27: Incorporated hysterix's support for symmetric encryption.\n* Did some internal cleanups of Unicode handling.\n\n0.2.6\n-----\n\nReleased: 2011-01-25\n\n* Fixed GC14: Should be able to accept passphrases from GPG-Agent.\n* Fixed GC19: Should be able to create a detached signature.\n* Fixed GC21/GC23: Better handling of less common responses from GPG.\n\n0.2.5\n-----\n\nReleased: 2010-10-13\n\n* Fixed GC11/GC16: Detached signatures can now be created.\n* Fixed GC3: Detached signatures can be verified.\n* Fixed GC12: Better support for RSA and IDEA.\n* Fixed GC15/GC17: Better support for non-ASCII input.\n\n0.2.4\n-----\n\nReleased: 2010-03-01\n\n* Fixed GC9: Now allows encryption without armor and the ability to encrypt\n  and decrypt directly to/from files.\n\n0.2.3\n-----\n\nReleased: 2010-01-07\n\n* Fixed GC7: Made sending data to process threaded and added a test case.\n  With a test data file used by the test case, the archive size has gone up\n  to 5MB (the size of the test file).\n\n0.2.2\n-----\n\nReleased: 2009-10-06\n\n* Fixed GC5/GC6: Added ``--batch`` when specifying ``--passphrase-fd`` and\n  changed the name of the distribution file to add the ``python-`` prefix.\n\n0.2.1\n-----\n\nReleased: 2009-08-07\n\n* Fixed GC2: Added ``handle_status()`` method to the ``ListKeys`` class.\n\n0.2.0\n-----\n\nReleased: 2009-07-16\n\n* Various changes made to support Python 3.0.\n\n0.1.0\n-----\n\nReleased: 2009-07-04\n\n* Initial release.\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest remote apidocs\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\napidocs:\n\tdocfrag --libs .. gnupg -f hovertip > hover.json\n\nremote:\n\trsync -avz $(BUILDDIR)/html/* vopal:~/apps/rdc_docs/python-gnupg\n\nspelling:\n\t$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/Distlib.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/Distlib.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/Distlib\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Distlib\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\tmake -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "docs/_static/sidebar.js",
    "content": "/*\n * sidebar.js\n * ~~~~~~~~~~\n *\n * This script makes the Sphinx sidebar collapsible.\n *\n * .sphinxsidebar contains .sphinxsidebarwrapper.  This script adds in\n * .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton used to\n * collapse and expand the sidebar.\n *\n * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden and the\n * width of the sidebar and the margin-left of the document are decreased.\n * When the sidebar is expanded the opposite happens.  This script saves a\n * per-browser/per-session cookie used to remember the position of the sidebar\n * among the pages.  Once the browser is closed the cookie is deleted and the\n * position reset to the default (expanded).\n *\n * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.\n * :license: BSD, see LICENSE for details.\n *\n */\n\n$(function() {\n  // global elements used by the functions.\n  // the 'sidebarbutton' element is defined as global after its\n  // creation, in the add_sidebar_button function\n  var bodywrapper = $('.bodywrapper');\n  var sidebar = $('.sphinxsidebar');\n  var sidebarwrapper = $('.sphinxsidebarwrapper');\n\n  // original margin-left of the bodywrapper and width of the sidebar\n  // with the sidebar expanded\n  var bw_margin_expanded = bodywrapper.css('margin-left');\n  var ssb_width_expanded = sidebar.width();\n\n  // margin-left of the bodywrapper and width of the sidebar\n  // with the sidebar collapsed\n  var bw_margin_collapsed = '.8em';\n  var ssb_width_collapsed = '.8em';\n\n  // colors used by the current theme\n  var dark_color = '#AAAAAA';\n  var light_color = '#CCCCCC';\n\n  function sidebar_is_collapsed() {\n    return sidebarwrapper.is(':not(:visible)');\n  }\n\n  function toggle_sidebar() {\n    if (sidebar_is_collapsed())\n      expand_sidebar();\n    else\n      collapse_sidebar();\n  }\n\n  function collapse_sidebar() {\n    sidebarwrapper.hide();\n    sidebar.css('width', ssb_width_collapsed);\n    bodywrapper.css('margin-left', bw_margin_collapsed);\n    sidebarbutton.css({\n        'margin-left': '0',\n        'height': bodywrapper.height(),\n        'border-radius': '5px'\n    });\n    sidebarbutton.find('span').text('»');\n    sidebarbutton.attr('title', _('Expand sidebar'));\n    document.cookie = 'sidebar=collapsed';\n  }\n\n  function expand_sidebar() {\n    bodywrapper.css('margin-left', bw_margin_expanded);\n    sidebar.css('width', ssb_width_expanded);\n    sidebarwrapper.show();\n    sidebarbutton.css({\n        'margin-left': ssb_width_expanded-12,\n        'height': bodywrapper.height(),\n        'border-radius': '0 5px 5px 0'\n    });\n    sidebarbutton.find('span').text('«');\n    sidebarbutton.attr('title', _('Collapse sidebar'));\n    //sidebarwrapper.css({'padding-top':\n    //  Math.max(window.pageYOffset - sidebarwrapper.offset().top, 10)});\n    document.cookie = 'sidebar=expanded';\n  }\n\n  function add_sidebar_button() {\n    sidebarwrapper.css({\n        'float': 'left',\n        'margin-right': '0',\n        'width': ssb_width_expanded - 28\n    });\n    // create the button\n    sidebar.append(\n      '<div id=\"sidebarbutton\"><span>&laquo;</span></div>'\n    );\n    var sidebarbutton = $('#sidebarbutton');\n    // find the height of the viewport to center the '<<' in the page\n    var viewport_height;\n    if (window.innerHeight)\n \t  viewport_height = window.innerHeight;\n    else\n\t  viewport_height = $(window).height();\n    var sidebar_offset = sidebar.offset().top;\n    var sidebar_height = Math.max(bodywrapper.height(), sidebar.height());\n    sidebarbutton.find('span').css({\n        'display': 'block',\n        'position': 'fixed',\n        'top': Math.min(viewport_height/2, sidebar_height/2 + sidebar_offset) - 10\n    });\n\n    sidebarbutton.click(toggle_sidebar);\n    sidebarbutton.attr('title', _('Collapse sidebar'));\n    sidebarbutton.css({\n        'border-radius': '0 5px 5px 0',\n        'color': '#444444',\n        'background-color': '#CCCCCC',\n        'font-size': '1.2em',\n        'cursor': 'pointer',\n        'height': sidebar_height,\n        'padding-top': '1px',\n        'padding-left': '1px',\n        'margin-left': ssb_width_expanded - 12\n    });\n\n    sidebarbutton.hover(\n      function () {\n          $(this).css('background-color', dark_color);\n      },\n      function () {\n          $(this).css('background-color', light_color);\n      }\n    );\n  }\n\n  function set_position_from_cookie() {\n    if (!document.cookie)\n      return;\n    var items = document.cookie.split(';');\n    for(var k=0; k<items.length; k++) {\n      var key_val = items[k].split('=');\n      var key = key_val[0];\n      if (key == 'sidebar') {\n        var value = key_val[1];\n        if ((value == 'collapsed') && (!sidebar_is_collapsed()))\n          collapse_sidebar();\n        else if ((value == 'expanded') && (sidebar_is_collapsed()))\n          expand_sidebar();\n      }\n    }\n  }\n\n  add_sidebar_button();\n  var sidebarbutton = $('#sidebarbutton');\n  set_position_from_cookie();\n});\n"
  },
  {
    "path": "docs/_templates/page.html",
    "content": "{% extends \"!page.html\" %}\n\n{% block body %}\n{{ super() }}\n<!-- div id=\"disqus_thread\"></div>\n<script type=\"text/javascript\">\n  /**\n    * var disqus_identifier; [Optional but recommended: Define a unique identifier (e.g. post id or slug) for this thread]\n    */\n  (function() {\n   var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;\n   dsq.src = 'http://python-distlib.disqus.com/embed.js';\n   (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n  })();\n</script>\n<noscript>Please enable JavaScript to view the <a href=\"http://disqus.com/?ref_noscript=python-distlib\">comments powered by Disqus.</a></noscript>\n<a href=\"http://disqus.com\" class=\"dsq-brlink\">Comments powered by <span class=\"logo-disqus\">Disqus</span></a -->\n\n{% endblock %}\n{% block footer %}\n{{ super() }}\n<!-- script type=\"text/javascript\">\nvar disqus_shortname = 'python-distlib';\n(function () {\n  var s = document.createElement('script'); s.async = true;\n  s.src = 'http://disqus.com/forums/python-distlib/count.js';\n  (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);\n}());\n</script -->\n{% endblock %}\n\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# GnuPG Wrapper for Python documentation build configuration file, created by\n# sphinx-quickstart on Thu Jul 24 04:03:38 2008.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport datetime, os, sys\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration -----------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.doctest',\n    'sphinx.ext.intersphinx',\n    'sphinx.ext.todo',\n    'sphinx.ext.coverage',\n    #'sphinx.ext.napoleon',\n    #'sphinx.ext.imgmath',\n    'sphinx.ext.ifconfig',\n    'sphinx.ext.viewcode',\n    'sphinxcontrib.spelling'\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'Python Wrapper for GnuPG'\ncopyright = u'2008-%s, Vinay Sajip' % datetime.date.today().year\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\nsys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))\nfrom gnupg import __version__ as release  # , __date__ as today\n\nversion = '.'.join(release.split('.')[:2])\nif '.dev' in release:\n    today = datetime.date.today().strftime('%b %d, %Y')\nelse:\n    today = today.split()[0][1:].split('-')\n    today = '%s %s, %s' % (today[1], today[0], today[2])\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\nadd_module_names = False\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\nspelling_lang = 'en_GB'\nspelling_word_list_filename = 'spelling_wordlist.txt'\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = os.environ.get('DOCS_THEME', 'default')\n\nTHEME_OPTIONS = {'sizzle': {}}\n\nif html_theme == 'sizzle' and os.path.isfile('hover.json'):\n    import json\n\n    with open('hover.json', encoding='utf-8') as f:\n        THEME_OPTIONS['sizzle']['custom_data'] = {'hovers': json.load(f)}\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\nif html_theme in THEME_OPTIONS:\n    html_theme_options = THEME_OPTIONS[html_theme]\n\n#html_theme_options = {'collapsiblesidebar': True}\n\n# Add any paths that contain custom themes here, relative to this directory.\nhtml_theme_path = ['themes']\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n\n# html_sidebars = {\n# '**': [\n# 'localtoc.html', 'globaltoc.html',  'relations.html',\n# 'sourcelink.html', 'searchbox.html'\n# ],\n# }\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'GnuPGWrapperforPythondoc'\n\n# -- Options for LaTeX output --------------------------------------------------\n\n# The paper size ('letter' or 'a4').\n#latex_paper_size = 'letter'\n\n# The font size ('10pt', '11pt' or '12pt').\n#latex_font_size = '10pt'\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    ('index', 'GnuPGWrapperforPython.tex', u'GnuPG Wrapper for Python Documentation', u'Vinay Sajip', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Additional stuff for the LaTeX preamble.\n#latex_preamble = ''\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [('index', 'python-gnupg', u'python-gnupg Documentation', [u'Vinay Sajip'], 1)]\n\n# -- Options for Epub output ---------------------------------------------------\n\n# Bibliographic Dublin Core info.\nepub_title = u'python-gnupg'\nepub_author = u'Vinay Sajip'\nepub_publisher = u'Vinay Sajip'\nepub_copyright = u'2019, Vinay Sajip'\n\n# The language of the text. It defaults to the language option\n# or en if the language is not set.\n#epub_language = ''\n\n# The scheme of the identifier. Typical schemes are ISBN or URL.\n#epub_scheme = ''\n\n# The unique identifier of the text. This can be a ISBN number\n# or the project homepage.\n#epub_identifier = ''\n\n# A unique identification for the text.\n#epub_uid = ''\n\n# HTML files that should be inserted before the pages created by sphinx.\n# The format is a list of tuples containing the path and title.\n#epub_pre_files = []\n\n# HTML files shat should be inserted after the pages created by sphinx.\n# The format is a list of tuples containing the path and title.\n#epub_post_files = []\n\n# A list of files that should not be packed into the epub file.\n#epub_exclude_files = []\n\n# The depth of the table of contents in toc.ncx.\n#epub_tocdepth = 3\n\n# Allow duplicate toc entries.\n#epub_tocdup = True\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping = {'python': ('http://docs.python.org/', None)}\n\n\ndef skip_module_docstring(app, what, name, obj, options, lines):\n    if (what, name) == ('module', 'distlib'):\n        del lines[:]\n\n\ndef setup(app):\n    app.connect('autodoc-process-docstring', skip_module_docstring)\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. GnuPG Wrapper for Python documentation master file, created by\n   sphinx-quickstart on Thu Jul 02 16:14:12 2009.\n\n###########################################\n`python-gnupg` - A Python wrapper for GnuPG\n###########################################\n\n.. rst-class:: release-info\n\n   .. list-table::\n      :widths: auto\n      :stub-columns: 1\n\n      * - Release:\n        - |release|\n      * - Date:\n        - |today|\n\n.. module:: gnupg\n   :synopsis: A Python wrapper for the GNU Privacy Guard (GnuPG)\n\n.. moduleauthor:: Vinay Sajip <vinay_sajip@red-dove.com>\n.. sectionauthor:: Vinay Sajip <vinay_sajip@red-dove.com>\n\n\n.. toctree::\n   :maxdepth: 4\n\nThe ``gnupg`` module allows Python programs to make use of the functionality provided\nby the `GNU Privacy Guard <https://gnupg.org/>`_ (abbreviated GPG or GnuPG). Using\nthis module, Python programs can encrypt and decrypt data, digitally sign documents\nand verify digital signatures, manage (generate, list and delete) encryption keys,\nusing Public Key Infrastructure (PKI) encryption technology based on OpenPGP.\n\nThis module is expected to be used with Python versions >= 3.6, or Python 2.7 for\nlegacy code. Install this module using ``pip install python-gnupg``. You can then use\nthis module in your own code by doing ``import gnupg`` or similar.\n\n.. _fork-note:\n\n.. note::\n   There is at least one fork of this project, which was apparently created because an\n   earlier version of this software used the ``subprocess`` module with\n   ``shell=True``, making it vulnerable to shell injection. **This is no longer the\n   case**.\n\n   Forks may not be drop-in compatible with this software, so take care to use the\n   correct version, as indicated in the ``pip install`` command above.\n\n.. index:: Deployment\n\n.. _deployment:\n\nDeployment Requirements\n=======================\n\nApart from a recent-enough version of Python, in order to use this module you need to\nhave access to a compatible version of the GnuPG executable. The system has been\ntested with GnuPG v1.4.9 on Windows and Ubuntu. On a Linux platform, this will\ntypically be installed via your distribution's package manager (e.g. ``apt-get`` on\nDebian/Ubuntu). Windows binaries are available `here\n<ftp://ftp.gnupg.org/gcrypt/binary/>`_ -- use one of the ``gnupg-w32cli-1.4.x.exe``\ninstallers for the simplest deployment options.\n\n.. note::\n   On Windows, it is *not* necessary to perform a full installation of GnuPG, using\n   the standard installer, on each computer: it is normally sufficient to distribute\n   only the executable, ``gpg.exe``, and a DLL which it depends on, ``iconv.dll``.\n   These files do not need to be placed in system directories, nor are registry\n   changes needed. The files need to be placed in a location such that implicit\n   invocation will find them - such as the working directory of the application which\n   uses the ``gnupg`` module, or on the system path if that is appropriate for your\n   requirements. Alternatively, you can specify the full path to the ``gpg``\n   executable. *Note, however, that if you want to use GnuPG 2.0, then this simple\n   deployment approach may not work, because there are more dependent files which you\n   have to ship. For this reason, our recommendation is to stick with GnuPG 1.4.x on\n   Windows, unless you specifically need 2.0 features - in which case, you may have to\n   do a full installation rather than just relying on a couple of files).*\n\n   Recent versions of GnuPG (>= 2.1.x) introduce a number of changes:\n\n   * By default, passphrases cannot be passed via streams to ``gpg`` unless the line\n     ``allow-loopback-pinentry`` is added to ``gpg-agent.conf`` in the home directory\n     used by ``gpg`` (this is also where the keyring files are kept). If that file\n     does not exist, you will need to create it with that single line. Note that even\n     with this configuration, some versions of GnuPG 2.1.x won't work as expected. In\n     our testing, we found, for example, that the 2.1.11 executable shipped with\n     Ubuntu 16.04 didn't behave helpfully, whereas a GnuPG 2.1.15 executable compiled\n     from source on the same machine worked as expected.\n   * To export secret keys, a passphrase must be provided.\n\n.. index:: Acknowledgements\n\nAcknowledgements\n================\n\nThis module is based on an earlier version, ``GPG.py``, written by Andrew Kuchling.\nThis was further improved by Richard Jones, and then even further by Steve Traugott.\nThe ``gnupg`` module is derived from `Steve Traugott's module\n<https://web.archive.org/web/20150310174851/http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py>`_\n(the original site no longer exists - this link is to the Wayback Machine), and uses\nPython's ``subprocess`` module to communicate with the GnuPG executable, which it uses\nto spawn a subprocess to do the real work.\n\nI've gratefully incorporated improvements contributed or suggested by:\n\n* Paul Cunnane (detached signature support)\n* Daniel Folkinshteyn (``recv_keys``, handling of subkeys and SIGEXPIRED, KEYEXPIRED\n  while verifying, EXPKEYSIG, REVKEYSIG)\n* Dmitry Gladkov (handle KEYEXPIRED when importing)\n* Abdul Karim (keyring patch)\n* Yann Leboulanger (handle ERRSIG and NO_PUBKEY while verifying, get subkeys)\n* Kirill Yakovenko (RSA and IDEA support)\n* Robert Leftwich (handle INV_SGNR, KEY_NOT_CREATED)\n* Michal Niklas (Trust levels for signature verification)\n* David Noël (``search_keys``, ``send_keys`` functionality)\n* David Andersen (handle UNEXPECTED during verification)\n* Jannis Leidel (output signature to a file)\n* Venzen Khaosan (scan_keys functionality)\n* Marcel Pörner (handle EXPORTED, EXPORT_RES)\n* Kévin Bernard-Allies (handle filename encoding under Windows)\n* Daniel Kahn Gillmor (various improvements which were released in 0.4.1)\n* William Foster (trust_key patch)\n\nand Google Code / BitBucket users\n\n* dprovins (ListKeys handle_status)\n* ernest0x (improved support for non-ASCII input)\n* eyepulp (additional options for encryption/decryption)\n* hysterix.is.slackin (symmetric encryption support)\n* natureshadow (improved status handling when smart cards in use)\n* SunDwarf (storing signatures against keys)\n\n(If I've missed anyone from this list, please let me know.)\n\n\nBefore you Start\n================\n\nGnuPG works on the basis of a \"home directory\" which is used to store public and\nprivate keyring files as well as a trust database. You need to identify in advance\nwhich directory on the end-user system will be used as the home directory, as you will\nneed to pass this information to ``gnupg``.\n\n.. index:: Getting started\n\nGetting Started\n===============\n\nYou interface to the GnuPG functionality through an instance of the ``GPG`` class::\n\n    >>> gpg = gnupg.GPG(gnupghome='/path/to/home/directory')\n\nIf the home directory does not exist, a ValueError will be raised. Thereafter, all the\noperations available are accessed via methods of this instance. If the ``gnupghome``\nparameter is omitted, GnuPG will use whatever directory is the default (consult the\nGnuPG documentation for more information on what this might be).\n\nThe :class:`GPG` constructor also accepts the following additional optional keyword\narguments:\n\ngpgbinary (defaults to \"gpg\")\n    The path to the ``gpg`` executable.\nverbose (defaults to ``False``)\n    Print information (e.g. the gpg command lines, and status messages returned by\n    gpg) to the console. You don't generally need to set this option, since the module\n    uses Python's ``logging`` package to provide more flexible functionality. The\n    status messages from ``gpg`` are quite voluminous, especially during key generation.\nuse_agent (defaults to ``False``)\n    If specified as True, the ``--use-agent`` parameter is passed to ``gpg``, asking it to\n    use any in-memory GPG agent (which remembers your credentials).\nkeyring (defaults to ``None``)\n    If specified, the value is used as the name of the keyring file. The default\n    keyring is not used. A list of paths to keyring files can also be specified.\noptions (defaults to ``None``)\n    If specified, the value should be a list of additional command-line options to\n    pass to ``gpg``.\nsecret_keyring (defaults to ``None``)\n    If specified, the value is used as the name of the secret keyring file. A list of\n    paths to secret keyring files can also be specified. *Note that these files are\n    not used by GnuPG >= 2.1.*\nenv  (defaults to ``None``)\n    If specified, the value is used as the environment variables used when calling the GPG\n    executable.\n\n.. versionchanged:: 0.3.4\n   The ``keyring`` argument can now also be a list of keyring filenames.\n\n.. versionadded:: 0.3.4\n   The ``secret_keyring`` argument was added. *Note that this argument is not used\n   when working with GnuPG >= 2.1.*\n\n.. note:: If you specify values in ``options``, make sure you don't specify values\n   which will conflict with other values added by ``python-gnupg``. You should be familiar\n   with GPG command-line arguments and how they affect GPG's operation.\n\n.. versionchanged:: 0.3.7\n   The default encoding was changed to ``latin-1``. In earlier versions, it was either\n   ``locale.getpreferredencoding()`` or, failing that, ``sys.stdin.encoding``, and\n   failing that, ``utf-8``.\n\n.. versionadded:: 0.5.0\n   The ``env`` argument was added.\n\nIf the ``gpgbinary`` executable cannot be found, a ``ValueError`` is raised in\n:meth:`GPG.__init__`.\n\nThe low-level communication between the ``gpg`` executable and ``python-gnupg`` is in\nterms of bytes, and ``python-gnupg`` tries to convert gpg's ``stderr`` stream to text\nusing an encoding. The default value of this is ``latin-1``, but you can override this\nby setting the encoding name in the GPG instance's ``encoding`` attribute after\ninstantiation, like this::\n\n    >>> gpg = gnupg.GPG(gnupghome='/path/to/home/directory')\n    >>> gpg.encoding = 'utf-8'\n\n.. note:: If you use the wrong encoding, you may get exceptions. The ``'latin-1'``\n   encoding leaves bytes as-is and shouldn't fail with encoding/decoding errors,\n   though it may not decode text correctly (so you may see odd characters in the\n   decoding output). The ``gpg`` executable will use an output encoding based on your\n   environment settings (e.g. environment variables, code page etc.) but defaults to\n   latin-1.\n\nFrom version 0.5.2 onwards, you can also control the buffer size for the I/O between\n``gpg`` and ``python-gnupg`` by setting the ``buffer_size`` attribute on a GPG instance.\nIt defaults to 16K.\n\n.. versionadded:: 0.5.2\n   The ``buffer_size`` attribute was added.\n\n\n.. index:: Key; management\n\nKey Management\n==============\n\nThe module provides functionality for generating (creating) keys, listing keys,\ndeleting keys, and importing and exporting keys.\n\n.. index:: Key; generating\n\nGenerating keys\n---------------\n\nThe first thing you typically want to do when starting with a PKI framework is to\ngenerate some keys. You can do this using the :meth:`~gnupg.GPG.gen_key` method::\n\n    >>> key = gpg.gen_key(input_data)\n\nwhere ``input_data`` is a special command string which tells GnuPG the\nparameters you want to use when creating the key. To make life easier, a helper\nmethod :meth:`~gnupg.GPG.gen_key_input` is provided which takes keyword\narguments which allow you to specify individual parameters of the key, as in\nthe following example::\n\n    >>> input_data = gpg.gen_key_input(key_type=\"RSA\", key_length=1024)\n\nSensible defaults are provided for parameters which you don't specify, as shown in the\nfollowing table.\n\n.. cssclass:: generic-table table-bordered table-responsive-sm table-striped mx-auto mb-3 colwidths-auto smaller hpad\n\n+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+\n| Parameter     | Keyword Argument | Default value           | Example values              | Meaning of parameter                        |\n+===============+==================+=========================+=============================+=============================================+\n| Key-Type      | key_type         | \"RSA\"                   | \"RSA\", \"DSA\"                | The type of the primary key to generate. It |\n|               |                  |                         |                             | must be capable of signing.                 |\n+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+\n| Key-Length    | key_length       | 1024                    | 1024, 2048                  | The length of the primary key in bits.      |\n+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+\n| Name-Real     | name_real        | \"Autogenerated Key\"     | \"Fred Bloggs\"               | The real name of the user identity which    |\n|               |                  |                         |                             | is represented by the key.                  |\n+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+\n| Name-Comment  | name_comment     | \"Generated by gnupg.py\" | \"A test user\"               | A comment to attach to the user id.         |\n+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+\n| Name-Email    | name_email       | <username>@<hostname>   | \"fred.bloggs\\@domain.com\"   | An email address for the user.              |\n+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+\n\nIf you don't specify any parameters, the values in the table above will be used with\nthe defaults indicated. There is a whole set of other parameters you can specify; see\n`this GnuPG document <https://github.com/gpg/gnupg/blob/master/doc/DETAILS>`_ for more\ndetails. While use of RSA keys is common (they can be used for both signing and\nencryption), another popular option is to use a DSA primary key (for signing) together\nwith a secondary El-Gamal key (for encryption). For this latter option, you could\nsupply the following additional parameters:\n\n.. cssclass:: generic-table table-bordered table-responsive-sm table-striped mx-auto mb-3 colwidths-auto smaller hpad\n\n+---------------+------------------+--------------------------------+---------------------------------------------+\n| Parameter     | Keyword Argument | Example values                 | Meaning of parameter                        |\n+===============+==================+================================+=============================================+\n| Subkey-Type   | subkey_type      | \"RSA\", \"ELG-E\"                 | The type of the secondary key to generate.  |\n+---------------+------------------+--------------------------------+---------------------------------------------+\n| Subkey-Length | subkey_length    | 1024, 2048                     | The length of the secondary key in bits.    |\n+---------------+------------------+--------------------------------+---------------------------------------------+\n| Expire-Date   | expire_date      | \"2009-12-31\", \"365d\", \"3m\",    | The expiration date for the primary and any |\n|               |                  | \"6w\", \"5y\", \"seconds=<epoch>\", | secondary key. You can specify an ISO date, |\n|               |                  | 0                              | A number of days/weeks/months/years, an     |\n|               |                  |                                | epoch value, or 0 for a non-expiring key.   |\n+---------------+------------------+--------------------------------+---------------------------------------------+\n| Passphrase    | passphrase       | \"secret\"                       | The passphrase to use. If this parameter is |\n|               |                  |                                | not specified, no passphrase is needed to   |\n|               |                  |                                | access the key. *Passphrases using newlines |\n|               |                  |                                | are not supported*. **Note that for GnuPG   |\n|               |                  |                                | versions >= 2.1, a passphrase must be       |\n|               |                  |                                | provided, unless extra steps are taken**:   |\n|               |                  |                                | see the ``no_protection`` argument, below.  |\n+---------------+------------------+--------------------------------+---------------------------------------------+\n| %no-protection| no_protection    | False (the default), True      | If no passphrase is wanted for a key (which |\n|               |                  |                                | might be the default for tests, say), or if |\n|               |                  |                                | you want to use an empty string as a        |\n|               |                  |                                | passphrase, then you should specify ``True``|\n|               |                  |                                | for this parameter. Otherwise, and if you   |\n|               |                  |                                | don't use pinentry to enter a passphrase,   |\n|               |                  |                                | then GnuPG >= 2.1 will not allow this. It   |\n|               |                  |                                | doesn't make sense to specify ``True`` if a |\n|               |                  |                                | non-empty passphrase is being supplied.     |\n+---------------+------------------+--------------------------------+---------------------------------------------+\n\nA complete list of key generation parameters can be found in the GnuPG documentation\n`here <https://www.gnupg.org/documentation/manuals/gnupg/Unattended-GPG-key-generation.html>`__.\n\n.. versionadded:: 0.4.7\n   The ``no_protection`` keyword argument was added.\n\nWhatever keyword arguments you pass to :meth:`~gnupg.GPG.gen_key_input` (other\nthan ``no_protection``) will be converted to the parameters expected by GnuPG\nby replacing underscores with hyphens and title-casing the result. You can of\ncourse construct the parameters in your own dictionary ``params`` and then pass\nit as follows::\n\n    >>> input_data = gpg.gen_key_input(**params)\n\n\nThe ``no_protection`` argument, if `True`, will be used to generate a\n`%no-protection` line which tells GnuPG that no protection with a\npassphrase is desired.\n\nThe return value from :meth:`~gnupg.GPG.gen_key` is an object whose\n`type` and `fingerprint` attributes indicate the type and fingerprint of the\ncreated key. If no key was created, these will be `None`.\n\n.. versionadded:: 0.4.9\n   There is now also a `status` attribute to the returned object which will be `'ok'`\n   if a key was created, `'key not created'` if that was reported by `gpg`, or `None`\n   in other cases.\n\n.. index::\n    single: Key; Generating subkeys\n\nGenerating subkeys\n^^^^^^^^^^^^^^^^^^\n\nTo generate a subkey for an already generated key use the\n:meth:`~gnupg.GPG.add_subkey` method::\n\n    >>> subkey = gpg.add_subkey(master_key) # same as gpg.add_subkey(master_key, None)\n    >>> subkey = gpg.add_subkey(master_key, master_key_password)\n\nThe :meth:`~gnupg.GPG.add_subkey` method has some additional keyword\narguments:\n\n* ``algorithm`` (defaulting to ``rsa``)\n* ``usage`` (defaulting to ``encrypt``)\n* ``expire`` (defaulting to ``-``)\n\nThe parameters are explained with every possible value in `this GnuPG documentation\n<https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html>`_\nunder ``quick-add-key``.\n\nIf you use the default algorithm, you'll get the default key size, which  is dependent\nupon the version of GnuPG that's used. If you want to specify the key size explicitly,\nyou can use values for ``algorithm`` incorporating both the algorithm itself and the\nkey size, as in the following examples.\n\n.. code-block:: python\n\n    gpg.add_subkey(..., algorithm='rsa2048')\n    gpg.add_subkey(..., algorithm='rsa3072')\n    gpg.add_subkey(..., algorithm='rsa4096')\n\n\n.. versionadded:: 0.4.9\n   The ``add_subkey`` method was added.\n\nSpecifying key usages\n^^^^^^^^^^^^^^^^^^^^^\n\nKeys can be used for some or all of encryption, signing or authentication.\nThese usages map onto flags assigned to a key - one or more of 'encrypt',\n'sign' and 'auth'. By default, nothing is specified, which assigns all flags to\na key. But sometimes you may want to depart from this behaviour. For example,\nif you create a subkey for encryption, then you probably don't want encryption\nto be enabled for the master key. You can specify the flags associated with a\nkey by passing a ``key_usage`` keyword argument to\n:meth:`~gnupg.GPG.gen_key_input` which provides one or more of the above flags\nin a space or comma-separated string, as in these example:\n\n.. code-block:: python\n\n    gpg.gen_key_input(..., key_usage='sign')\n    gpg.gen_key_input(..., key_usage='sign encrypt')\n    gpg.gen_key_input(..., key_usage='sign, auth')\n\nThis corresponds to the ``usage`` parameter of :meth:`~gnupg.GPG.add_subkey`,\ndescribed earlier. Note that you still need to ensure that the key type of the\nkey being created is appropriate for the usages.\n\n.. index::\n    single: Key; elliptic curves\n    single: ECC keys\n\nGenerating elliptic curve keys\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo generate keys with elliptic curves, pass a `key_curve` keyword parameter to\n:meth:`~gnupg.GPG.gen_key_input` and omit `key_length`. For example,\n`key_curve='cv25519'` or `key_type='ECDSA', key_curve='nistp384'`. Refer to\n`GnuPG resources <https://wiki.gnupg.org/ECC>`_ to see which options are\nsupported. Note that you'll need GnuPG >= 2.1 for this to work.\n\nSupplemental information on the aliases used for key types and curves is given\n`here\n<https://www.gnupg.org/documentation/manuals/gcrypt/ECC-key-parameters.html>`__.\nYou can use the curve type alias in the ``algorithm`` argument to\n:meth:`~gnupg.GPG.add_subkey`, as in the following example.\n\n.. code-block:: python\n\n    input_data = gpg.gen_key_input(key_type='EDDSA', key_curve='ed25519' ...)\n    master_key = gpg.gen_key(input_data)\n    subkey = gpg.add_subkey(master_key.fingerprint, algorithm='cv25519' ...)\n\n.. index::\n    single: Key; performance issues\n    single: Entropy\n\nPerformance Issues\n^^^^^^^^^^^^^^^^^^\n\nKey generation requires the system to work with a source of random numbers. Systems\nwhich are better at generating random numbers than others are said to have higher\n*entropy*. This is typically obtained from the system hardware; the GnuPG\ndocumentation recommends that keys be generated *only* on a local machine (i.e. not\none being accessed across a network), and that keyboard, mouse and disk activity be\nmaximised during key generation to increase the entropy of the system.\n\nUnfortunately, there are some scenarios - for example, on virtual machines which don't\nhave real hardware - where insufficient entropy causes key generation to be\n*extremely* slow. If you come across this problem, you should investigate means of\nincreasing the system entropy. On virtualised Linux systems, this can often be\nachieved by installing the ``rng-tools`` package. This is available at least on\nRPM-based and APT-based systems (Red Hat/Fedora, Debian, Ubuntu and derivative\ndistributions).\n\n.. index:: Key; exporting\n\nExporting keys\n--------------\n\nTo export keys, use the :meth:`~gnupg.GPG.export_keys` method::\n\n    >>> ascii_armored_public_keys = gpg.export_keys(keyids) # same as gpg.export_keys(keyids, False)\n    >>> ascii_armored_private_keys = gpg.export_keys(keyids, True) # True => private keys\n\nFor the ``keyids`` parameter, you can use a sequence of anything which GnuPG itself\naccepts to identify a key - for example, the keyid or the fingerprint could be used.\nIf you want to pass a single keyid, then you can just pass in a string which\nidentifies the key. If you pass an empty list in ``keyids``, all keys are exported.\n\nThe :meth:`~gnupg.GPG.export_keys` method has some additional keyword arguments:\n\n* ``armor`` (defaulting to ``True``) - when ``True``, passes ``--armor`` to ``gpg``.\n* ``minimal`` (defaulting to ``False``) - when ``True``, passes\n  ``--export-options export-minimal`` to ``gpg``.\n* ``passphrase`` - if specified, sends the specified passphrase to ``gpg``. For\n  GnuPG >= 2.1, exporting secret keys requires a passphrase to be provided.\n* ``expect_passphrase`` - defaults to ``True`` for backward compatibility. If the\n  passphrase is to be passed to ``gpg`` via pinentry, you wouldn't pass it here - so\n  specify ``expect_passphrase=False`` in that case. If you don't do that, and don't\n  pass a passphrase, a ``ValueError`` will be raised.\n* ``output`` - defaults to ``None``, but if specified, should be the pathname of a file\n  to which the exported keys should be written.\n\n.. versionadded:: 0.3.7\n   The ``armor`` and ``minimal`` keyword arguments were added.\n\n.. versionadded:: 0.4.0\n   The ``passphrase`` keyword argument was added.\n\n.. versionadded:: 0.4.2\n   The ``expect_passphrase`` keyword argument was added.\n\n.. versionadded:: 0.5.1\n   The ``output`` keyword argument was added.\n\n.. index:: Key; importing\n\n.. index:: Key; receiving\n\nImporting and receiving keys\n----------------------------\n\nTo import keys, get the key data as an ASCII string, say ``key_data``. Then you\ncan call :meth:`~gnupg.GPG.import_keys` with it::\n\n    >>> import_result = gpg.import_keys(key_data)\n\nThis will import all the keys in ``key_data``. The number of keys imported will be\navailable in ``import_result.count`` and the fingerprints of the imported keys will be\nin ``import_result.fingerprints``.\n\nIn addition, ``extra_args`` and ``passphrase`` keyword parameter can be specified. If\nprovided, ``extra_args`` is treated as a list of additional arguments to pass to the\n``gpg`` executable. If ``passphrase`` is specified, it is passed to ``gpgg`` for when\nan imported secret key has a passphrase.\n\n.. versionadded:: 0.4.5\n   The ``extra_args`` keyword argument.\n\n.. versionadded:: 0.4.7\n   The ``passphrase`` keyword argument.\n\nTo import keys from a file, use :meth:`~gnupg.GPG.import_keys_file` instead::\n\n    >>> import_result = gpg.import_keys_file(key_path)\n\nThis also takes the keyword arguments specified for :meth:`~gnupg.GPG.import_keys`.\n\n.. versionadded:: 0.5.0\n   The :meth:`~gnupg.GPG.import_keys_file` method.\n\nTo receive keys from a keyserver, use :meth:`~gnupg.GPG.recv_keys`::\n\n    >>> import_result = gpg.recv_keys('server-name', 'keyid1', 'keyid2', ...)\n\nThis will fetch keys with all specified keyids and import them. Note that on Windows,\nyou may require helper programs such as ``gpg_hkp.exe``, distributed with GnuPG, to\nsuccessfully run ``recv_keys``. On Jython, security permissions may lead to failure of\n``recv_keys``.\n\nNote that when you import keys, you may get spurious \"key expired\" / \"signature\nexpired\" messages which are sent by ``gpg`` and collected by ``python-gnupg``. This\nmay happen, for example, if there are subkey expiry dates which have been extended, so\nthat the keys haven't actually expired, even when ``gpg`` sends messages that they\nhave. Make sure you just look at the ``count`` and ``fingerprints`` attributes to\nidentify the keys that were imported.\n\n.. index:: Key; listing\n\nListing keys\n------------\n\nNow that we've seen how to generate, import and export keys, let's move on to\nfinding which keys we have in our keyrings. This is fairly straightforward\nusing the :meth:`~gnupg.GPG.list_keys` method::\n\n    >>> public_keys = gpg.list_keys() # same as gpg.list_keys(False)\n    >>> private_keys = gpg.list_keys(True) # True => private keys\n\nThe returned value from :meth:`~gnupg.GPG.list_keys` is a subclass of Python's\n``list`` class. Each entry represents one key and is a Python dictionary which\ncontains useful information about the corresponding key.\n\nThe following entries are in the returned dictionary. Some of the key names are not\nideal for describing the values, but they have been left as is for backward\ncompatibility reasons. As `GnuPG documentation\n<https://github.com/gpg/gnupg/blob/master/doc/DETAILS>`_ has improved, a better\nunderstanding is possible of the information returned by ``gpg``.\n\n.. cssclass:: generic-table table-bordered table-responsive-sm table-striped mx-auto mb-3 colwidths-auto smaller hpad\n\n+-------------+--------------------------------------------------------------------------------+\n| dict key    | dict value (all string values)                                                 |\n+=============+================================================================================+\n| type        | Type of key                                                                    |\n+-------------+--------------------------------------------------------------------------------+\n| trust       | The validity of the key                                                        |\n+-------------+--------------------------------------------------------------------------------+\n| length      | The length of the key in bits                                                  |\n+-------------+--------------------------------------------------------------------------------+\n| algo        | Public key algorithm                                                           |\n+-------------+--------------------------------------------------------------------------------+\n| keyid       | The key ID                                                                     |\n+-------------+--------------------------------------------------------------------------------+\n| date        | The creation date of the key in UTC as a Unix timestamp                        |\n+-------------+--------------------------------------------------------------------------------+\n| expires     | The expiry date of the key in UTC as a timestamp, if specified                 |\n+-------------+--------------------------------------------------------------------------------+\n| dummy       | Certificate serial number, UID hash or trust signature info                    |\n+-------------+--------------------------------------------------------------------------------+\n| ownertrust  | The level of owner trust for the key                                           |\n+-------------+--------------------------------------------------------------------------------+\n| uid         | The user ID                                                                    |\n+-------------+--------------------------------------------------------------------------------+\n| sig         | Signature class                                                                |\n+-------------+--------------------------------------------------------------------------------+\n| cap         | Key capabilities                                                               |\n+-------------+--------------------------------------------------------------------------------+\n| issuer      | Issuer information                                                             |\n+-------------+--------------------------------------------------------------------------------+\n| flag        | A flag field                                                                   |\n+-------------+--------------------------------------------------------------------------------+\n| token       | Token serial number                                                            |\n+-------------+--------------------------------------------------------------------------------+\n| hash        | Hash algorithm                                                                 |\n+-------------+--------------------------------------------------------------------------------+\n| curve       | Curve name for elliptic curve cryptography (ECC) keys                          |\n+-------------+--------------------------------------------------------------------------------+\n| compliance  | Compliance flags                                                               |\n+-------------+--------------------------------------------------------------------------------+\n| updated     | Last updated timestamp                                                         |\n+-------------+--------------------------------------------------------------------------------+\n| origin      | Origin of keys                                                                 |\n+-------------+--------------------------------------------------------------------------------+\n| keygrip     | Keygrip of keys (Note that you'll need GnuPG >= 2.1 for this to work.)         |\n+-------------+--------------------------------------------------------------------------------+\n| subkeys     | A list containing [keyid, type, fingerprint, keygrip] elements for each subkey |\n+-------------+--------------------------------------------------------------------------------+\n| subkey_info | A dictionary of subkey information keyed on subkey id                          |\n+-------------+--------------------------------------------------------------------------------+\n\nDepending on the version of ``gpg`` used, some of these keys may have the value\n``'unavailable'``. The last two keys are provided by ``python-gnupg`` rather than\n``gpg``.\n\nFor more information about the values in this dictionary, refer to the GnuPG\ndocumentation linked above. (Note that that documentation is not terribly\nuser-friendly, but nevertheless it should be usable.)\n\nThe returned value from :meth:`~gnupg.GPG.list_keys` has an attribute ``uids``, which is a\nlist of userids associated with the listed keys, and an attribute ``fingerprints``, which\nis a list of the key fingerprints associated with the listed keys.\n\n.. versionadded:: 0.3.8\n   The returned value from :meth:`~gnupg.GPG.list_keys` now has a new\n   attribute, ``key_map``, which is a dictionary mapping key and subkey\n   fingerprints to the corresponding key's dictionary. With this change, you\n   don't need to iterate over the (potentially large) returned list to search\n   for a key with a given fingerprint - the ``key_map`` dict will take you\n   straight to the key info, whether the fingerprint you have is for a key or a\n   subkey.\n\n.. versionadded:: 0.3.8\n   You can also list a subset of keys by specifying a ``keys=`` keyword\n   argument to :meth:`~gnupg.GPG.list_keys` whose value is either a single\n   string matching a key, or a list of strings matching multiple keys. In this\n   case, the return value only includes matching keys.\n\n.. versionadded:: 0.3.9\n   A new ``sigs=`` keyword argument has been added to\n   :meth:`~gnupg.GPG.list_keys`, defaulting to ``False``. If you specify true,\n   the ``sigs`` entry in the key information returned will contain a list of\n   signatures which apply to the key. Each entry in the list is a 3-tuple of\n   (``keyid``, ``user-id``, ``signature-class``) where the ``signature-class``\n   is as defined by RFC-4880_.\n\n   It doesn't make sense to supply both ``secret=True`` *and* ``sigs=True`` (people\n   can't sign your secret keys), so in case ``secret=True`` is specified, the\n   ``sigs=`` value has no effect.\n\n.. versionadded:: 0.4.1\n   Instances of the ``GPG`` class now have an additional ``on_data`` attribute, which\n   defaults to ``None``. It can be set to a callable which will be called with a\n   single argument - a binary chunk of data received from the ``gpg`` executable. The\n   callable can do whatever it likes with the chunks passed to it - e.g. write them to\n   a separate stream. The callable should not raise any exceptions (unless it wants\n   the current operation to fail).\n\n.. versionadded:: 0.4.2\n   Information on keys returned by :meth:`~gnupg.GPG.list_keys` or\n   :meth:`~gnupg.GPG.scan_keys` now incudes a ``subkey_info`` dictionary, which\n   contains any returned information on subkeys such as creation and expiry\n   dates. The dictionary is keyed on the subkey ID. The following additional\n   keys are present in key information dictionaries: ``cap``, ``issuer``,\n   ``flag``, ``token``, ``hash``, ``curve``, ``compliance``, ``updated`` and\n   ``origin``.\n\n.. versionadded:: 0.4.4\n   Instances of the ``GPG`` class now have an additional\n   ``check_fingerprint_collisions`` attribute, which defaults to ``False``. If set to\n   a truthy value, fingerprint collisions are checked for (and a ``ValueError`` raised\n   if a collision is detected) when listing or scanning keys. It appears that ``gpg``\n   is quite lenient about allowing duplicated keys in keyrings, which would lead to\n   collisions.\n\n.. versionchanged:: 0.4.4\n   The ``on_data`` callable will now be called with an empty chunk when the data\n   stream from ``gpg`` is exhausted. It can now also return a value: if the value\n   ``False`` is returned, the chunk will *not* be buffered within ``python-gnupg``.\n   This might be useful if you want to do your own buffering or avoid buffering\n   altogether. If any other value is returned (including the value ``None``, for\n   backward compatibility) the chunk will be buffered as normal by ``python-gnupg``.\n\n.. versionadded:: 0.4.6\n   Instances of the ``GPG`` class now have an additional ``error_map`` attribute,\n   which defaults to ``None``. If you set this, the value should be a dictionary\n   mapping error codes to error messages. The source distribution includes a file\n   ``messages.json`` which contains such a mapping, gleaned from the GnuPG library\n   libgpg-error, version 1.37. The test suite shows how to convert that JSON to a form\n   suitable for converting to an ``error_map`` value (basically, converting the string\n   keys in the JSON to integers using base 16).\n\n.. versionadded:: 0.4.9\n   Information on keys returned by :meth:`~gnupg.GPG.list_keys` now includes\n   the ``keygrip`` attribute. The ``subkeys`` attribute now also consists of four\n   values with the ``keygrip`` being the fourth. Note that you'll need GnuPG >=\n   2.1 for this to work.\n\n.. versionadded:: 0.5.4\n   Instances of the result classes from operations now have an ``on_data_failure``\n   attribute, which defaults to ``None``. If an ``on_data`` callable raises an exception,\n   the ``on_data_failure`` attribute of the returned object from a high-level operation\n   is set to the first exception that was raised. The ``on_data`` callable will continue\n   to be called with future chunks. If you use ``on_data`` with code that can raise any\n   exceptions, be sure to check the ``on_data_failure`` attribute of a returned object\n   before using any other aspects of the result.\n\n.. versionadded:: 0.5.5\n   The returned value from :meth:`~gnupg.GPG.list_keys` now has a new\n   attribute, ``uid_map``, which is a dictionary mapping uids to dicts with\n   detailed information about the corresponding uid. The keys of the information\n   provided are listed in the table above. Refer to the `GnuPG documentation\n   <https://github.com/gpg/gnupg/blob/master/doc/DETAILS>`_ for more information.\n\n   You could use this information as in the following example:\n\n    .. code-block:: pycon\n\n        >>> from pprint import pprint\n        >>> keys = gpg.list_keys()\n        >>> pprint(keys.uids)\n        ['Andrew Able (A test user) <andrew.able@alpha.com>',\n         'Barb Bruin <barb.bruin@beta.com>',\n         'Babs Broon <babs.broon@beta.com>',\n         'Barbara Brown (A test user) <barbara.brown@beta.com>',\n         'Charlie Clark (A test user) <charlie.clark@gamma.com>',\n         'Donna Davis (A test user) <donna.davis@delta.com>']\n        >>> pprint(keys.uid_map('Barbara Brown (A test user) <barbara.brown@beta.com>')\n        {'algo': '',\n         'date': '1739485458',\n         'dummy': '8B989767967370B894C53279A3BDF655F00CD4DE',\n         'expires': '',\n         'keyid': '',\n         'length': '',\n         'ownertrust': '',\n         'sig': '',\n         'trust': 'u',\n         'type': 'uid',\n         'uid': 'Barbara Brown (A test user) <barbara.brown@beta.com>'}\n        >>> pprint(keys.uid_map['Barb Bruin <barb.bruin@beta.com>'])\n        {'algo': '',\n         'date': '1739485886',\n         'dummy': '951261047308BCA0B45FD738AD8630B336B88ECF',\n         'expires': '',\n         'keyid': '',\n         'length': '',\n         'ownertrust': '',\n         'sig': '',\n         'trust': 'u',\n         'type': 'uid',\n         'uid': 'Barb Bruin <barb.bruin@beta.com>'}\n        >>> pprint(keys.uid_map['Babs Broon <babs.broon@beta.com>'])\n        {'algo': '',\n         'date': '',\n         'dummy': '2BDB74660AC54DF33DE523429386E2D460904E74',\n         'expires': '',\n         'keyid': '',\n         'length': '',\n         'ownertrust': '',\n         'sig': '',\n         'trust': 'r',\n         'type': 'uid',\n         'uid': 'Babs Broon <babs.broon@beta.com>'}\n        >>>\n\n    The first two of these dictionaries show normal uids (trust is 'u', for ultimate), whereas the third\n    shows a revoked uid (trust is 'r', for revoked).\n\n\n.. _RFC-4880: https://tools.ietf.org/html/rfc4880#section-5.2.1\n\n\n.. index:: Key; trusting\n\nSetting the trust level for imported keys\n-----------------------------------------\n\nYou can set the trust level for imported keys using\n:meth:`~gnupg.GPG.trust_keys`::\n\n    >>> gpg.trust_keys(fingerprints, trustlevel)\n\nwhere the ``fingerprints`` are a list of fingerprints of keys for which the trust\nlevel is to be set, and ``trustlevel`` is one of the string values ``'TRUST_EXPIRED'``,\n``'TRUST_UNDEFINED'``, ``'TRUST_NEVER'``, ``'TRUST_MARGINAL'``, ``'TRUST_FULLY'`` or\n``'TRUST_ULTIMATE'``.\n\nYou can also specify a single fingerprint for the ``fingerprints`` parameter.\n\n.. versionadded:: 0.4.2\n   The ``trust_keys`` method was added.\n\n.. index:: Key; scanning\n\nScanning keys\n-------------\n\nWe can also scan keys in files without importing them into a local keyring, by\nusing :meth:`~gnupg.GPG.scan_keys`::\n\n    >>> keys = gpg.scan_keys(key_file_name)\n\nThe returned value from :meth:`~gnupg.GPG.scan_keys` has the same format as for\n:meth:`~gnupg.GPG.list_keys`.\n\n.. versionadded:: 0.3.7\n   The :meth:`~gnupg.GPG.scan_keys` method was added.\n\nTo scan keys in a string, we can use :meth:`~gnupg.GPG.scan_keys_mem` instead::\n\n    >>> keys = gpg.scan_keys_mem(key_text)\n\nThe result will be the same as for :meth:`~gnupg.GPG.scan_keys`.\n\n.. versionadded:: 0.5.1\n   The :meth:`~gnupg.GPG.scan_keys_mem` method was added.\n\n.. index:: Key; deleting\n\nDeleting keys\n-------------\n\nTo delete keys, their key identifiers must be specified. If a public/private keypair\nhas been created, a private key needs to be deleted before the public key can be\ndeleted, and for both you use the :meth:`~gnupg.GPG.delete_keys` method::\n\n    >>> key = gpg.gen_key(gpg.gen_key_input())\n    >>> fp = key.fingerprint\n    >>> str(gpg.delete_keys(fp)) # same as gpg.delete_keys(fp, False)\n    'Must delete secret key first'\n    >>> str(gpg.delete_keys(fp, True))# True => private keys\n    'ok'\n    >>> str(gpg.delete_keys(fp))\n    'ok'\n    >>> str(gpg.delete_keys(\"nosuchkey\"))\n    'No such key'\n\nThe argument you pass to :meth:`~gnupg.GPG.delete_keys` can be either a single\nkey identifier (e.g. keyid or fingerprint) or a sequence of key identifiers.\n\nThe :meth:`~gnupg.GPG.delete_keys` method has some additional keyword arguments:\n\n* ``passphrase`` - if specified, sends the specified passphrase to ``gpg``. For GnuPG\n  >= 2.1, exporting secret keys requires a passphrase to be provided.\n* ``expect_passphrase`` - defaults to ``True`` for backward compatibility. If the\n  passphrase is to be passed to ``gpg`` via pinentry, you wouldn't pass it here - so\n  specify ``expect_passphrase=False`` in that case. If you don't do that, and don't\n  pass a passphrase, a ``ValueError`` will be raised.\n* ``exclamation_mode`` - defaults to ``False`` for backward compatibility. If the exclamation\n  mode is set, and a fingerprint of a subkey is passed only that subkey will be deleted. If the\n  fingerprint is of a primary key the entire key will be deleted.\n\n.. versionadded:: 0.4.0\n   The ``passphrase`` keyword argument was added.\n\n.. versionadded:: 0.4.2\n   The ``expect_passphrase`` keyword argument was added.\n\n.. versionadded:: 0.4.9\n   The ``exclamation_mode`` keyword argument was added.\n\n\n.. index:: Key; searching\n\nSearching for keys\n------------------\n\nYou can search for keys by passing a search query and optionally a keyserver\nname to the :meth:`~gnupg.GPG.search_keys`. If no keyserver is specified,\n``pgp.mit.edu`` is used. A list of dictionaries describing keys that were found\nis returned (this list could be empty). For example::\n\n    >>> gpg.search_keys('vinay_sajip@hotmail.com', 'keyserver.ubuntu.com')\n    [{'keyid': u'92905378', 'uids': [u'Vinay Sajip <vinay_sajip@hotmail.com>'], 'expires': u'', 'length': u'1024', 'algo': u'17', 'date': u'1221156445', 'type': u'pub'}]\n\n.. versionadded:: 0.3.5\n   The :meth:`~gnupg.GPG.search_keys` method was added.\n\n.. index:: Key; sending\n\nSending keys\n------------\n\nYou can send keys to a keyserver by passing its name and some key identifiers\nto the :meth:`~gnupg.GPG.send_keys`. For example::\n\n    >>> gpg.send_keys('keyserver.ubuntu.com', '6E4D5A2B')\n    <gnupg.SendResult object at 0xb74d55ac>\n\n.. versionadded:: 0.3.5\n   The :meth:`~gnupg.GPG.send_keys` method was added.\n\n\nEncryption and Decryption\n=========================\n\nData intended for some particular recipients is encrypted with the public keys of\nthose recipients. Each recipient can decrypt the encrypted data using the\ncorresponding private key.\n\n.. index:: Encryption\n\nEncryption\n----------\n\nTo encrypt a message, use the :meth:`~gnupg.GPG.encrypt` method::\n\n    >>> encrypted_ascii_data = gpg.encrypt(data, recipients)\n\nIf you want to encrypt data in a file (or file-like object), use\n:meth:`~gnupg.GPG.encrypt_file` instead::\n\n    >>> encrypted_ascii_data = gpg.encrypt_file(stream, recipients) # e.g. after stream = open(filename, 'rb')\n\nThese methods both return an object such that:\n\n* If encryption succeeded, the returned object's ``ok`` attribute is set to ``True``\n  and the ``data`` attribute holds the encrypted data.\n  Otherwise, the returned object's ``ok`` attribute is set to ``False`` and its\n  ``status`` attribute (a message string) provides more information as to the reason\n  for failure (for example, ``'invalid recipient'`` or ``'key expired'``).\n* ``str(encrypted_ascii_data)`` gives the encrypted data in a non-binary format.\n\nIn both cases, ``recipients`` is a list of key fingerprints for those recipients. For\nyour convenience, if there is a single recipient, you can pass the fingerprint rather\nthan a 1-element array containing the fingerprint. Both methods accept the following\noptional keyword arguments:\n\nsign (defaults to ``None``)\n    Either the Boolean value ``True``, or the fingerprint of a key which is used to\n    sign the encrypted data. If ``True`` is specified, the default key is used for\n    signing. When not specified, the data is not signed.\nalways_trust (defaults to ``False``)\n    Skip key validation and assume that used keys are always fully trusted.\npassphrase (defaults to ``None``)\n    A passphrase to use when accessing the keyrings.\nextra_args (defaults to ``None``)\n    A list of additional arguments to pass to the ``gpg`` executable. For example, you\n    could pass ``extra_args=['-z', '0']`` to disable compression, or you could pass\n    ``extra_args=['--set-filename', 'name-to-embed-in-encrypted-file.txt']`` to embed\n    a specific file name in the encrypted message.\narmor (defaults to ``True``)\n    Whether to use ASCII armor. If ``False``, binary data is produced.\noutput (defaults to ``None``)\n    The name of an output file to write to. If a name is specified, the encrypted\n    output is written directly to the file.\n\n.. index:: Encryption; symmetric\n\nsymmetric (defaults to ``False``)\n    If specified, symmetric encryption is used. In this case, specify recipients as\n    ``None``. If ``True`` is specified, then the default cipher algorithm (``CAST5``)\n    is used. Starting with version 0.3.5, you can also specify the cipher-algorithm to\n    use (for example, ``'AES256'``). Check your ``gpg`` command line help to see what\n    symmetric cipher algorithms are supported. Note that the default (``CAST5``) may\n    not be the best available.\n\n.. versionchanged:: 0.3.5\n   A string can be passed for the ``symmetric`` argument, as well as ``True`` or\n   ``False``. If a string is passed, it should be a symmetric cipher algorithm\n   supported by the ``gpg`` you are using.\n\n.. versionadded:: 0.4.1\n   The ``extra_args`` keyword argument was added.\n\n.. versionadded:: 0.5.1\n   The ``status_detail`` attribute was added to the result object. This attribute will\n   be set when the result object's ``status`` attribute is set to ``invalid recipient``\n   and will contain more information about the failure in the form of ``reason:ident``\n   where ``reason`` is a text description of the reason, and ``ident`` identifies the\n   recipient key.\n\n.. note:: Any public key provided for encryption should be trusted, otherwise\n   encryption fails but without any warning. This is because gpg just prints a message\n   to the console, but does not provide a specific error indication that the Python\n   wrapper can use.\n\n.. versionchanged:: 0.5.0\n   The `stream` argument to :meth:`~gnupg.GPG.encrypt_file` can be a pathname\n   to an existing file as well as text or a file-like object. In the pathname\n   case, ``python-gnupg`` will open and close the file for you.\n\n.. note::\n   ``python-gnupg`` assumes that any object with a :attr:`read` attribute is a\n   file-like object. Otherwise, if it corresponds to an existing file, then it is taken\n   as a filename, and otherwise it must be the actual data to be processed.\n\n\n.. index:: Decryption\n\nDecryption\n----------\n\nTo decrypt a message, use the :meth:`~gnupg.GPG.decrypt` method::\n\n    >>> decrypted_data = gpg.decrypt(data)\n\nIf you want to decrypt data in a file (or file-like object), use\n:meth:`~gnupg.GPG.decrypt_file` instead::\n\n    >>> decrypted_data = gpg.decrypt_file(stream) # e.g. after stream = open(filename, 'rb')\n\nThese methods both return an object such that ``str(decrypted_data)`` gives the\ndecrypted data in a non-binary format. If decryption succeeded, the returned object's\n``ok`` attribute is set to ``True`` and the ``data`` attribute holds the decrypted\ndata. Otherwise, the returned object's ``ok`` attribute is set to ``False`` and its\n``status`` attribute (a message string) provides more information as to the reason for\nfailure (for example, ``'bad passphrase'`` or ``'decryption failed'``).\n\nBoth methods accept the following optional keyword arguments:\n\nalways_trust (defaults to ``False``)\n    Skip key validation and assume that used keys are always fully trusted.\npassphrase (defaults to ``None``)\n    A passphrase to use when accessing the keyrings.\nextra_args (defaults to ``None``)\n    A list of additional arguments to pass to the ``gpg`` executable.\noutput (defaults to ``None``)\n    The name of an output file to write to. If a name is specified, the decrypted\n    output is written directly to the file.\n\n.. versionadded:: 0.4.1\n   The ``extra_args`` keyword argument was added.\n\n.. versionadded:: 0.4.2\n   Upon a successful decryption, the keyid of the decrypting key is stored in the\n   ``key_id`` attribute of the result, if this information is provided by ``gpg``.\n\n.. versionchanged:: 0.5.0\n   The `stream` argument to :meth:`~gnupg.GPG.decrypt_file` can be a pathname\n   to an existing file as well as text or a file-like object. In the pathname\n   case, ``python-gnupg`` will open and close the file for you.\n\n.. _caching-warning:\n\n.. warning::\n   **Passphrase caching:** By default, ``gpg-agent`` caches passphrases, and this can\n   lead to unexpected results such as successfully decrypting messages even when\n   passing the wrong passphrase. To avoid this, disable caching by putting the\n   following two lines in ``gpg-agent.conf``:\n\n   * ``default-cache-ttl 0`` and either\n   * ``maximum-cache-ttl 0`` for GnuPG < 2.1, or\n   * ``max-cache-ttl 0`` for GnuPG >= 2.1.\n\n   For more information, see the `GnuPG documentation on agent configuration\n   <https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html>`_.\n\nUsing signing and encryption together\n-------------------------------------\n\nIf you want to use signing and encryption together, use the\n:meth:`~gnupg.GPG.encrypt` with a signer fingerprint and the corresponding\npassphrase::\n\n    >>> encrypted_data = gpg.encrypt(data, recipients, sign=signer_fingerprint, passphrase=signer_passphrase)\n\nThe resulting encrypted data contains the signature. When decrypting the data, upon\nsuccessful decryption, signature verification is also performed (assuming the relevant\npublic keys are available at the recipient end). The results are stored in the object\nreturned from the :meth:`~gnupg.GPG.decrypt` call::\n\n    >>> decrypted_data = gpg.decrypt(data, passphrase=recipient_passphrase)\n\nAt this point, if a signature is verified, signer information is held in attributes of\n``decrypted_data``: ``username``, ``key_id``, ``signature_id``, ``fingerprint``,\n``trust_level`` and ``trust_text``. If the message wasn't signed, these attributes\nwill all be set to ``None``.\n\nThe trust levels are (in increasing order) ``TRUST_UNDEFINED``, ``TRUST_NEVER``,\n``TRUST_MARGINAL``, ``TRUST_FULLY`` and ``TRUST_ULTIMATE``. If verification succeeded,\nyou can test the trust level against known values as in the following example::\n\n    decrypted_data = gpg.decrypt(data, passphrase=recipient_passphrase))\n    if decrypted_data.trust_level is not None and decrypted_data.trust_level >= decrypted_data.TRUST_FULLY:\n        print('Trust level: %s' % decrypted_data.trust_text)\n\n.. versionadded:: 0.3.1\n   The ``trust_level`` and ``trust_text`` attributes were added.\n\n\nFinding the recipients for an encrypted message\n-----------------------------------------------\n\nSometimes, it's desirable to find the recipients for an encrypted message,\nwithout actually performing decryption. You can do this using the\n:meth:`~gnupg.GPG.get_recipients` or\n:meth:`~gnupg.GPG.get_recipients_file` methods:\n\n    >>> ids = gpg.get_recipients(data)\n\nor, with a file or file-like object:\n\n    >>> ids = gpg.get_recipients_file(stream) # e.g. after stream = open(filename, 'rb')\n\n.. versionadded:: 0.4.8\n   The ``get_recipients`` and ``get_recipients_file`` methods were added.\n\n.. versionchanged:: 0.5.0\n   The `stream` argument to :meth:`~gnupg.GPG.get_recipients_file` can be a\n   pathname to an existing file as well as text or a file-like object. In the\n   pathname case, ``python-gnupg`` will open and close the file for you.\n\n\nCustom handling of data streams\n-------------------------------\n\nDuring processing, ``gpg`` often sends output to its ``stdout`` stream, which is captured\nby ``python-gnupg`` buffered, and returned as part of an operation's result (usually in\nthe ``data`` attribute). However, there might be times when you want to:\n\n* Avoid buffering, as the data sizes involved are large.\n* Process the data as it becomes available, before it's all available at the end of an\n  operation. Most commonly, this will happen during decryption.\n\nIn such cases, you can supply a callable in the ``on_data`` attribute of a :class:`GPG`\ninstance before you invoke the operation. When an operation with ``gpg`` is initiated, if\n``on_data`` is given a value, it will be called with each chunk of data (of type\n``bytes``) received from ``gpg``, and its return value will be used to determine whether\n``python-gnupg`` buffers the data. At the end of the data stream, it will be called with\na zero-length bytestring (allowing you do any necessary clean-up).\n\nIf the ``on_data`` callable returns ``False``, the data will *not* be buffered by\n``python-gnupg``. For any other return value (including ``None``), the data *will* be\nbuffered. (This slightly odd arrangement is for backwards compatibility.)\n\nExample usages (not tested, error handling omitted):\n\n.. code-block:: python\n\n    # Doing your own buffering in memory\n\n    chunks = []\n\n    def collector(chunk):\n        chunks.append(chunk)\n        return False  # Tell python-gnupg not to buffer the chunk\n\n    gpg = GPG(...)\n    gpg.on_data = collector\n    gpg.decrypt(...)\n\n    # Doing your own buffering in a file\n\n    class Collector:\n        def __init__(self, fn):\n            self.out = open(fn, 'wb')\n\n        def __call__(self, chunk):\n            self.out.write(chunk)\n            if not chunk:\n                self.out.close()\n            return False  # Tell python-gnupg not to buffer the chunk\n\n    gpg = GPG(...)\n    gpg.on_data = Collector('/tmp/plain.txt')\n    gpg.decrypt(...)\n\n    # Processing as you go (assuming the decrypted data is utf-8 encoded)\n\n    import codecs\n\n    class Processor:\n        def __init__(self, fn):\n            self.out = open(fn, 'w', encoding='utf-8')\n            self.decoder =  codecs.getincrementaldecoder('utf-8')\n            self.result = ''\n\n        def __call__(self, chunk):\n            final = (len(chunk) == 0)\n            self.result += self.decoder.decode(chunk, final)\n            # Perhaps do custom processing of self.result here\n            self.out.write(self.result)\n            self.result = ''\n            if final:\n                self.out.close()\n            return False  # Tell python-gnupg not to buffer the chunk\n\n    gpg = GPG(...)\n    gpg.on_data = Processor('/tmp/plain.txt')\n    gpg.decrypt(...)\n\n\nThreading constraints on processing data\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe `on_data` callable is called from a background thread which is reading data from the\n`gpg` child process. Sometimes, there are constraints on where certain processing can be\ndone (e.g. code involving SQLAlchemy sessions or Qt GUI updates needs to be run on a\nspecific thread, not just any thread). To handle this, you can use a queue, as in this\nexample (not tested, error handling omitted):\n\n.. code-block:: python\n\n    import queue\n\n    class ChunkForwarder:\n        def __init__(self, queue):\n            self.queue = queue\n\n        def __call__(self, chunk):\n            self.queue.put(chunk)\n            return False  # Tell python-gnupg not to buffer the chunk\n\n    # In the thread where you need to process chunks\n    gpg = GPG(...)\n    q = queue.Queue()\n    gpg.on_data = ChunkForwarder(q)\n    # call the operation you want to perform. Chunks will be sent to the queue.\n    gpg.decrypt(...)\n    while True:\n        chunk = q.get()\n        q.task_done()  # keep things tidy\n        if not chunk:\n            break\n        # process the chunk using a SQLAlchemy session, Qt widget or whatever\n        process_chunk(chunk)\n\n\nSigning and Verification\n========================\n\nData intended for digital signing is signed with the private key of the signer. Each\nrecipient can verify the signed data using the corresponding public key.\n\n.. index:: Signing\n\nSigning\n-------\n\nTo sign a message, use the :meth:`~gnupg.GPG.sign` method::\n\n    >>> signed_data = gpg.sign(message)\n\nor, for data in a file (or file-like object), you can use the\n:meth:`~gnupg.GPG.sign_file` method instead::\n\n    >>> signed_data = gpg.sign_file(stream) # e.g. after stream = open(filename, \"rb\")\n\nThese methods both return an object such that ``str(signed_data)`` gives the signed\ndata in a non-binary format. They accept the following optional keyword arguments:\n\nkeyid (defaults to ``None``)\n    The id for the key which will be used to do the signing. If not specified, the\n    first key in the secret keyring is used.\npassphrase (defaults to ``None``)\n    A passphrase to use when accessing the keyrings.\nclearsign (defaults to ``True``)\n    Returns a clear text signature, i.e. one which can be read without any special\n    software.\ndetach (defaults to ``False``)\n    Returns a detached signature. If you specify ``True`` for this, then the detached\n    signature will not be clear text, i.e. it will be as if you had specified a\n    ``False`` value for *clearsign*. This is because if both are specified, gpg\n    ignores the request for a detached signature.\nbinary (defaults to ``False``)\n    If ``True``, a binary signature (rather than armored ASCII) is created.\noutput (defaults to ``None``)\n    If specified, this is used as the file path where GPG outputs the signature.\n    Convention dictates a ``.asc`` or ``.sig`` file extension for this.\nextra_args (defaults to ``None``)\n    A list of additional arguments to pass to the ``gpg`` executable.\n\nNote: If the data being signed is binary, calling ``str(signed_data)`` may raise\nexceptions. In that case, use the fact that ``signed_data.data`` holds the binary\nsigned data. Usually the signature itself is ASCII; it's the message itself which may\ncause the exceptions to be raised. (Unless a detached signature is requested, the\nresult of signing is the message with the signature appended.)\n\nThe hash algorithm used when creating the signature can be found in the\n``signed_data.hash_algo`` attribute.\n\n.. versionadded:: 0.2.5\n   The ``detach`` keyword argument was added in version 0.2.5.\n\n.. versionadded:: 0.2.6\n   The ``binary`` keyword argument was added in version 0.2.6.\n\n.. versionadded:: 0.3.7\n   The ``output`` keyword argument was added in version 0.3.7.\n\n.. versionadded:: 0.4.1\n   The ``extra_args`` keyword argument was added.\n\n.. versionadded:: 0.4.2\n   The keyid and username of the signing key are stored in the ``key_id`` and\n   ``username`` attributes of the result, if this information is provided by ``gpg``\n   (which should happen if you specify ``extra_args=['--verbose']``).\n\n.. versionchanged:: 0.5.0\n   The *stream* argument to :meth:`~gnupg.GPG.sign_file` can be a pathname to\n   an existing file as well as text or a file-like object. In the pathname\n   case, ``python-gnupg`` will open and close the file for you.\n\n.. versionadded:: 0.5.1\n   The ``status_detail`` attribute was added to the result object. This attribute will\n   be set when the result object's ``status`` attribute is set to ``invalid signer``\n   and will contain more information about the failure in the form of ``reason:ident``\n   where ``reason`` is a text description of the reason, and ``ident`` identifies the\n   signing key.\n\n.. index:: Verification\n\nVerification\n------------\n\nTo verify some data which you've received, use the\n:meth:`~gnupg.GPG.verify` method::\n\n    >>> verified = gpg.verify(data)\n\nTo verify data in a file (or file-like object), use :meth:`~gnupg.GPG.verify_file`::\n\n    >>> verified = gpg.verify_file(stream) # e.g. after stream = open(filename, \"rb\")\n\nYou can use the returned value in a Boolean context::\n\n    >>> if not verified: raise ValueError(\"Signature could not be verified!\")\n\nGetting the signed data out while verifying\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you clearsign data, the signature envelops the signed data (whether text or\nbinary) with the signature, but by default you won't get this data back from a\n:meth:`~gnupg.GPG.verify` or :meth:`~gnupg.GPG.verify_file` call. In order to\nextract the signed data, you need to pass more information to the ``verify``\nmethods about where you want that data (if none is specified, the data is\ndiscarded). To write it to ``gpg``'s standard output, specify\n``extra_args=['-o', '-']``. In that case, it will be returned as a bytestring\nin ``verified.data``. Alternatively, to write to a file, you can pass\n``extra_args=['-o', 'path/to/write/data.to']`` and it will be written to the\nfile you specify. (Thanks to Mark Neil for this suggestion.)\n\n\nVerifying detached signatures on disk\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you want to verify a detached signature, use :meth:`~gnupg.GPG.verify_file`::\n\n    >>> verified = gpg.verify_file(stream, path_to_data_file)\n\nNote that in this case, the ``stream`` contains the *signature* to be verified. The\ndata that was signed should be in a separate file whose path is indicated by\n``path_to_data_file``.\n\n.. versionadded:: 0.2.5\n   The second argument to verify_file (``data_filename``) was added.\n\n.. versionadded:: 0.4.1\n   An optional keyword argument to verify_file (``close_file``) was added. This\n   defaults to ``True``, but if set to ``False``, the signature stream is not closed.\n   It's then left to the caller to close it when appropriate.\n\n   An optional keyword argument ``extra_args`` was added. This defaults to ``None``,\n   but if a value is specified, it should be a list of extra arguments to pass to the\n   ``gpg`` executable.\n\n.. versionadded:: 0.4.4\n   When signature verification is performed, multiple signatures might be present.\n   Information about all signatures is now captured in a ``sig_info`` attribute of the\n   value returned from ``verify``. This is a dictionary keyed by the signature ID and\n   whose values are dictionaries containing the following information (note - all are\n   string values):\n\n   * ``fingerprint`` - the fingerprint of the signing key. * ``pubkey_fingerprint`` -\n     this is usually the same as ``fingerprint``, but it might be different if a\n     subkey was used for the signing.\n\n   * ``keyid`` - the key id.\n\n   * ``username`` - user information for the signing key.\n\n   * ``status`` - this indicates the status of the signature.\n\n   * ``creation_date`` - the creation date of the signature in text format, YYYY-MM-DD.\n\n   * ``timestamp`` - the signature creation time as a timestamp.\n\n   * ``expiry`` - the signature expiry time as a timestamp, or ``'0'`` to\n     indicate no expiry.\n\n   * ``trust_level`` - the trust level, see below.\n\n   * ``trust_text`` - the text corresponding to the trust level.\n\n   Note that only information for valid signatures will be present in ``sig_info``.\n\nWhen a signature is verified, signer information is held in attributes of\n``verified``: ``username``, ``key_id``, ``signature_id``, ``fingerprint``,\n``trust_level`` and ``trust_text``. If the message wasn't signed, these attributes\nwill all be set to ``None``. If there were multiple signatures, the last values seen\nwill be shown.\n\nThe trust levels are (in increasing order) TRUST_UNDEFINED, TRUST_NEVER,\nTRUST_MARGINAL, TRUST_FULLY and TRUST_ULTIMATE. If verification succeeded, you can\ntest the trust level against known values as in the following example::\n\n    verified = gpg.verify(data)\n    if verified.trust_level is not None and verified.trust_level >= verified.TRUST_FULLY:\n        print('Trust level: %s' % verified.trust_text)\n\n.. versionadded:: 0.3.1\n   The ``trust_level`` and ``trust_text`` attributes were added.\n\nNote that even if you have a valid signature, you may want to not rely on that\nvalidity, if the key used for signing has expired or was revoked. If this information\nis available, it will be in the ``key_status`` attribute =, and the result will still\nbe ``False`` in a Boolean context. If there is no problem detected with the signing\nkey, the ``key_status`` attribute will be ``None``.\n\n.. versionadded:: 0.3.3\n   The ``key_status`` attribute was added.\n\n.. versionadded:: 0.4.2\n   The keyid and username of the signing key are stored in the ``key_id`` and\n   ``username`` attributes of the result, if this information is provided by ``gpg``.\n\n.. versionchanged:: 0.5.0\n   The `stream` argument to :meth:`~gnupg.GPG.verify_file` can be a pathname to\n   an existing file as well as text or a file-like object. In the pathname\n   case, ``python-gnupg`` will open and close the file for you.\n\n.. versionadded:: 0.5.1\n   A ``problems`` attribute was added which holds problems reported by ``gpg``\n   during verification. This is a list of dictionaries, one for each reported\n   problem. Each dictionary will have ``status`` and ``keyid`` keys indicating\n   the problem and the corresponding key; other information in the dictionaries\n   will be error specific.\n\n\nVerifying detached signatures in memory\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYou can also verify detached signatures where the data is in memory, using\n:meth:`~gnupg.GPG.verify_data`::\n\n    >>> verified = gpg.verify_data(path_to_signature_file, data)\n\nwhere *data* should be a byte string of the data to be verified against the signature\nin the file named by *path_to_signature_file*. The returned value is the same as for\nthe other verification methods.\n\nIn addition, an ``extra_args`` keyword parameter can be specified. If provided, this\nis treated as a list of additional arguments to pass to the ``gpg`` executable.\n\n.. versionadded:: 0.3.6\n   The :meth:`~gnupg.GPG.verify_data` method was added.\n\n.. versionadded:: 0.4.1\n   The ``extra_args`` keyword argument was added.\n\nAccessing gpg's Return Code\n===========================\n\nStarting with version 0.4.8, return values to all calls which implement ``gpg``\noperations, other than :meth:`~gnupg.GPG.export_keys`, will have a ``returncode``\nattribute which is the return code returned by the ``gpg`` invocation made to perform the\noperation (the result of :meth:`~gnupg.GPG.export_keys` is the set of exported keys and\ndoesn't have this attribute).\n\n.. versionadded:: 0.4.8\n    The ``returncode`` attribute was added to result instances.\n\nPassphrases\n===========\n\nPassphrases provided to ``python-gnupg`` are not stored persistently, and just passed\nthrough to the ``GnuPG`` executable through a pipe. The user of ``python-gnupg``  is\nresponsible for taking care not to store passphrases where they may become available\nto malicious code or malicious users, as well as the physical and security aspects of\nmanaging their private keys.\n\n\n.. index:: Logging\n\n.. _logging:\n\nLogging\n=======\n\nThe module makes use of the facilities provided by Python's ``logging`` package. A\nsingle logger is created with the module's ``__name__``, hence ``gnupg`` unless you\nrename the module. A ``NullHandler`` instance is added to this logger, so if you don't\nuse logging in your application which uses this module, you shouldn't see any logging\nmessages. If you do use logging in your application, just configure it in the normal\nway.\n\n.. index:: Download\n\nTest Harness\n============\n\nThe distribution includes a test harness, ``test_gnupg.py``, which contains unit tests\ncovering the functionality described above. You can invoke ``test_gnupg.py`` with one\nor more optional command-line arguments. If no arguments are provided, all tests are\nrun. If arguments are provided, they collectively determine which of the tests will be\nrun:\n\nimport\n    Run tests relating to key import\ncrypt\n    Run tests relating to encryption and decryption\nsign\n    Run tests relating to signing and verification\nkey\n    Run tests relating to key management\nbasic\n    Run basic tests relating to environment setup, or which don't fit into one of the\n    above categories\n\nDownload\n========\n\nThe latest version is available from the `PyPI\n<https://pypi.python.org/pypi/python-gnupg>`_ page.\n\nThe source code repository can be found `here\n<https://github.com/vsajip/python-gnupg/>`__.\n\nStatus and Further Work\n=======================\n\nThe ``gnupg`` module, being based on proven earlier versions, is quite usable, and\ncomes packaged with Linux distributions such as Debian, Ubuntu and Fedora. However,\nthere may be some features of GnuPG which this module does not take advantage of, or\nprovide access to. How this module evolves will be determined by feedback from its\nuser community.\n\nSupport for GnuPG 2.1 is limited, because that version of GnuPG does not provide the\nability to prevent pinentry popups in all cases. This package sends passphrases to the\n``gpg`` executable via pipes, which is only possible under GnuPG 2.1 under limited\nconditions and requiring end-users to edit GnuPG configuration files.\n\nAt present, functionality that requires interacting with the ``gpg`` executable (e.g.\nfor key editing) is not available. This is because it requires essentially a state\nmachine which manages the interaction - moreover, a state machine which varies\naccording to the specific version of the ``gpg`` executable being used.\n\nIf you find bugs and want to raise issues, please do so via the `project issue tracker\n<https://github.com/vsajip/python-gnupg/issues/new/choose>`_.\n\nAll feedback will be gratefully received; please send it to the `discussion group\n<https://groups.google.com/group/python-gnupg>`_.\n\n.. cssclass:: hidden\n\nIndex\n=====\n\n* :ref:`genindex`\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "sphinxcontrib-spelling==7.6.2\nsphinx<7\nsphinx-rtd-theme>=1.2.2\n"
  },
  {
    "path": "docs/spelling_wordlist.txt",
    "content": "Cunnane\nFolkinshteyn\nDmitry\nGladkov\nAbdul\nKarim\nYann\nLeboulanger\nKirill\nYakovenko\nLeftwich\nMichal\nNiklas\nNoël\nJannis\nLeidel\nVenzen\nKhaosan\nPörner\nKévin\ndprovins\nernest\neyepulp\nhysterix\nslackin\nnatureshadow\ngpgbinary\ngpg\nenv\nKuchling\nTraugott\nWayback\ngnupg\netc\nlatin\nAutogenerated\nBloggs\nGamal\npinentry\nkeyid\nkeyids\nJython\nalgo\nownertrust\nuid\nsig\nKeygrip\nkeygrip\ntruthy\nlibgpg\nth\narmor\nclearsign\narmored\nargs\npopups\n"
  },
  {
    "path": "gnupg.py",
    "content": "\"\"\" A wrapper for the GnuPG `gpg` command.\n\nPortions of this module are derived from A.M. Kuchling's well-designed\nGPG.py, using Richard Jones' updated version 1.3, which can be found\nin the pycrypto CVS repository on Sourceforge:\n\nhttp://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py\n\nThis module is *not* forward-compatible with amk's; some of the\nold interface has changed.  For instance, since I've added decrypt\nfunctionality, I elected to initialize with a 'gnupghome' argument\ninstead of 'keyring', so that gpg can find both the public and secret\nkeyrings.  I've also altered some of the returned objects in order for\nthe caller to not have to know as much about the internals of the\nresult classes.\n\nWhile the rest of ISconf is released under the GPL, I am releasing\nthis single file under the same terms that A.M. Kuchling used for\npycrypto.\n\nSteve Traugott, stevegt@terraluna.org\nThu Jun 23 21:27:20 PDT 2005\n\nThis version of the module has been modified from Steve Traugott's version\n(see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by\nVinay Sajip to make use of the subprocess module (Steve's version uses os.fork()\nand so does not work on Windows). Renamed to gnupg.py to avoid confusion with\nthe previous versions.\n\nModifications Copyright (C) 2008-2026 Vinay Sajip. All rights reserved.\n\nFor the full documentation, see https://docs.red-dove.com/python-gnupg/ or\nhttps://gnupg.readthedocs.io/\n\"\"\"\n\nimport codecs\nfrom datetime import datetime\nfrom email.utils import parseaddr\nfrom io import StringIO\nimport logging\nimport os\ntry:\n    from queue import Queue, Empty\nexcept ImportError:\n    from Queue import Queue, Empty\nimport re\nimport socket\nfrom subprocess import Popen, PIPE\nimport sys\nimport threading\n\n__version__ = '0.5.7.dev0'\n__author__ = 'Vinay Sajip'\n__date__ = '$31-Dec-2025 16:41:34$'\n\nSTARTUPINFO = None\nif os.name == 'nt':  # pragma: no cover\n    try:\n        from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW, SW_HIDE\n    except ImportError:\n        STARTUPINFO = None\n\ntry:\n    unicode\n    _py3k = False\n    string_types = basestring\n    text_type = unicode\n    path_types = (bytes, str)\nexcept NameError:\n    _py3k = True\n    string_types = str\n    text_type = str\n    path_types = (str, )\n\nlogger = logging.getLogger(__name__)\nif not logger.handlers:\n    logger.addHandler(logging.NullHandler())\n\n# See gh-196: Logging could show sensitive data. It also produces some voluminous\n# output. Hence, split into two tiers - stuff that's always logged, and stuff that's\n# only logged if log_everything is True. (This is set by the test script.)\n#\n# For now, only debug logging of chunks falls into the optionally-logged category.\nlog_everything = False\n\n# We use the test below because it works for Jython as well as CPython\nif os.path.__name__ == 'ntpath':  # pragma: no cover\n    # On Windows, we don't need shell quoting, other than worrying about\n    # paths with spaces in them.\n    def shell_quote(s):\n        return '\"%s\"' % s\nelse:\n    # Section copied from sarge\n\n    # This regex determines which shell input needs quoting\n    # because it may be unsafe\n    UNSAFE = re.compile(r'[^\\w%+,./:=@-]')\n\n    def shell_quote(s):\n        \"\"\"\n        Quote text so that it is safe for POSIX command shells.\n\n        For example, \"*.py\" would be converted to \"'*.py'\". If the text is considered safe it is returned unquoted.\n\n        Args:\n            s (str): The value to quote\n        Returns:\n            str: A safe version of the input, from the point of view of POSIX\n                 command shells.\n        \"\"\"\n        if not isinstance(s, string_types):  # pragma: no cover\n            raise TypeError('Expected string type, got %s' % type(s))\n        if not s:  # pragma: no cover\n            result = \"''\"\n        elif not UNSAFE.search(s):  # pragma: no cover\n            result = s\n        else:\n            result = \"'%s'\" % s.replace(\"'\", r\"'\\''\")\n        return result\n\n    # end of sarge code\n\n# Now that we use shell=False, we shouldn't need to quote arguments.\n# Use no_quote instead of shell_quote to remind us of where quoting\n# was needed. However, note that we still need, on 2.x, to encode any\n# Unicode argument with the file system encoding - see Issue #41 and\n# Python issue #1759845 (\"subprocess.call fails with unicode strings in\n# command line\").\n\n# Allows the encoding used to be overridden in special cases by setting\n# this module attribute appropriately.\nfsencoding = sys.getfilesystemencoding()\n\n\ndef no_quote(s):\n    \"\"\"\n    Legacy function which is a no-op on Python 3.\n    \"\"\"\n    if not _py3k and isinstance(s, text_type):\n        s = s.encode(fsencoding)\n    return s\n\n\ndef _copy_data(instream, outstream, buffer_size, error_queue):\n    # Copy one stream to another\n    assert buffer_size > 0\n    sent = 0\n    if hasattr(sys.stdin, 'encoding'):\n        enc = sys.stdin.encoding\n    else:  # pragma: no cover\n        enc = 'ascii'\n    while True:\n        # See issue #39: read can fail when e.g. a text stream is provided\n        # for what is actually a binary file\n        try:\n            data = instream.read(buffer_size)\n        except Exception as e:  # pragma: no cover\n            logger.warning('Exception occurred while reading', exc_info=1)\n            error_queue.put_nowait(e)\n            break\n        if not data:\n            break\n        sent += len(data)\n        # logger.debug('sending chunk (%d): %r', sent, data[:256])\n        try:\n            outstream.write(data)\n        except UnicodeError:  # pragma: no cover\n            outstream.write(data.encode(enc))\n        except Exception as e:  # pragma: no cover\n            # Can sometimes get 'broken pipe' errors even when the data has all\n            # been sent\n            logger.exception('Error sending data')\n            error_queue.put_nowait(e)\n            break\n    try:\n        outstream.close()\n    except IOError:  # pragma: no cover\n        logger.warning('Exception occurred while closing: ignored', exc_info=1)\n    logger.debug('closed output, %d bytes sent', sent)\n\n\ndef _threaded_copy_data(instream, outstream, buffer_size, error_queue):\n    assert buffer_size > 0\n    wr = threading.Thread(target=_copy_data, args=(instream, outstream, buffer_size, error_queue))\n    wr.daemon = True\n    logger.debug('data copier: %r, %r, %r', wr, instream, outstream)\n    wr.start()\n    return wr\n\n\ndef _write_passphrase(stream, passphrase, encoding):\n    passphrase = '%s\\n' % passphrase\n    passphrase = passphrase.encode(encoding)\n    stream.write(passphrase)\n    logger.debug('Wrote passphrase')\n\n\ndef _is_sequence(instance):\n    return isinstance(instance, (list, tuple, set, frozenset))\n\n\ndef _make_memory_stream(s):\n    try:\n        from io import BytesIO\n        rv = BytesIO(s)\n    except ImportError:  # pragma: no cover\n        rv = StringIO(s)\n    return rv\n\n\ndef _make_binary_stream(s, encoding):\n    if _py3k:\n        if isinstance(s, str):\n            s = s.encode(encoding)\n    else:\n        if type(s) is not str:\n            s = s.encode(encoding)\n    return _make_memory_stream(s)\n\n\nclass StatusHandler(object):\n    \"\"\"\n    The base class for handling status messages from `gpg`.\n    \"\"\"\n\n    on_data_failure = None  # set at instance level when failures occur\n\n    def __init__(self, gpg):\n        \"\"\"\n        Initialize an instance.\n\n        Args:\n            gpg (GPG): The :class:`GPG` instance in use.\n        \"\"\"\n        self.gpg = gpg\n\n    def handle_status(self, key, value):\n        \"\"\"\n        Handle status messages from the `gpg` child process. These are lines of the format\n\n            [GNUPG:] <key> <value>\n\n        Args:\n            key (str): Identifies what the status message is.\n            value (str): Identifies additional data, which differs depending on the key.\n        \"\"\"\n        raise NotImplementedError\n\n\nclass Verify(StatusHandler):\n    \"\"\"\n    This class handles status messages during signature verificaton.\n    \"\"\"\n\n    TRUST_EXPIRED = 0\n    TRUST_UNDEFINED = 1\n    TRUST_NEVER = 2\n    TRUST_MARGINAL = 3\n    TRUST_FULLY = 4\n    TRUST_ULTIMATE = 5\n\n    TRUST_LEVELS = {\n        'TRUST_EXPIRED': TRUST_EXPIRED,\n        'TRUST_UNDEFINED': TRUST_UNDEFINED,\n        'TRUST_NEVER': TRUST_NEVER,\n        'TRUST_MARGINAL': TRUST_MARGINAL,\n        'TRUST_FULLY': TRUST_FULLY,\n        'TRUST_ULTIMATE': TRUST_ULTIMATE,\n    }\n\n    # for now, just the most common error codes. This can be expanded as and\n    # when reports come in of other errors.\n    GPG_SYSTEM_ERROR_CODES = {\n        1: 'permission denied',\n        35: 'file exists',\n        81: 'file not found',\n        97: 'not a directory',\n    }\n\n    GPG_ERROR_CODES = {\n        11: 'incorrect passphrase',\n    }\n\n    returncode = None\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.valid = False\n        self.fingerprint = self.creation_date = self.timestamp = None\n        self.signature_id = self.key_id = None\n        self.username = None\n        self.key_id = None\n        self.key_status = None\n        self.status = None\n        self.pubkey_fingerprint = None\n        self.expire_timestamp = None\n        self.sig_timestamp = None\n        self.trust_text = None\n        self.trust_level = None\n        self.sig_info = {}\n        self.problems = []\n\n    def __nonzero__(self):  # pragma: no cover\n        return self.valid\n\n    __bool__ = __nonzero__\n\n    def handle_status(self, key, value):\n\n        def update_sig_info(**kwargs):\n            sig_id = self.signature_id\n            if sig_id:\n                info = self.sig_info[sig_id]\n                info.update(kwargs)\n            else:\n                logger.debug('Ignored due to missing sig iD: %s', kwargs)\n\n        if key in self.TRUST_LEVELS:\n            self.trust_text = key\n            self.trust_level = self.TRUST_LEVELS[key]\n            update_sig_info(trust_level=self.trust_level, trust_text=self.trust_text)\n            # See Issue #214. Once we see this, we're done with the signature just seen.\n            # Zap the signature ID, because we don't see a SIG_ID unless we have a new\n            # good signature.\n            self.signature_id = None\n        elif key in ('WARNING', 'ERROR'):  # pragma: no cover\n            logger.warning('potential problem: %s: %s', key, value)\n        elif key == 'BADSIG':  # pragma: no cover\n            self.valid = False\n            self.status = 'signature bad'\n            self.key_id, self.username = value.split(None, 1)\n            self.problems.append({'status': self.status, 'keyid': self.key_id, 'user': self.username})\n            update_sig_info(keyid=self.key_id, username=self.username, status=self.status)\n        elif key == 'ERRSIG':  # pragma: no cover\n            self.valid = False\n            parts = value.split()\n            (self.key_id, algo, hash_algo, cls, self.timestamp) = parts[:5]\n            # Since GnuPG 2.2.7, a fingerprint is tacked on\n            if len(parts) >= 7:\n                self.fingerprint = parts[6]\n            self.status = 'signature error'\n            update_sig_info(keyid=self.key_id,\n                            timestamp=self.timestamp,\n                            fingerprint=self.fingerprint,\n                            status=self.status)\n            self.problems.append({\n                'status': self.status,\n                'keyid': self.key_id,\n                'timestamp': self.timestamp,\n                'fingerprint': self.fingerprint\n            })\n        elif key == 'EXPSIG':  # pragma: no cover\n            self.valid = False\n            self.status = 'signature expired'\n            self.key_id, self.username = value.split(None, 1)\n            update_sig_info(keyid=self.key_id, username=self.username, status=self.status)\n            self.problems.append({'status': self.status, 'keyid': self.key_id, 'user': self.username})\n        elif key == 'GOODSIG':\n            self.valid = True\n            self.status = 'signature good'\n            self.key_id, self.username = value.split(None, 1)\n            update_sig_info(keyid=self.key_id, username=self.username, status=self.status)\n        elif key == 'VALIDSIG':\n            parts = value.split()\n            fingerprint, creation_date, sig_ts, expire_ts = parts[:4]\n            (self.fingerprint, self.creation_date, self.sig_timestamp,\n             self.expire_timestamp) = (fingerprint, creation_date, sig_ts, expire_ts)\n            # may be different if signature is made with a subkey\n            if len(parts) >= 10:\n                self.pubkey_fingerprint = parts[9]\n            self.status = 'signature valid'\n            update_sig_info(fingerprint=fingerprint,\n                            creation_date=creation_date,\n                            timestamp=sig_ts,\n                            expiry=expire_ts,\n                            pubkey_fingerprint=self.pubkey_fingerprint,\n                            status=self.status)\n        elif key == 'SIG_ID':\n            sig_id, creation_date, timestamp = value.split()\n            self.sig_info[sig_id] = {'creation_date': creation_date, 'timestamp': timestamp}\n            (self.signature_id, self.creation_date, self.timestamp) = (sig_id, creation_date, timestamp)\n        elif key == 'NO_PUBKEY':  # pragma: no cover\n            self.valid = False\n            self.key_id = value\n            self.status = 'no public key'\n            self.problems.append({'status': self.status, 'keyid': self.key_id})\n        elif key == 'NO_SECKEY':  # pragma: no cover\n            self.valid = False\n            self.key_id = value\n            self.status = 'no secret key'\n            self.problems.append({'status': self.status, 'keyid': self.key_id})\n        elif key in ('EXPKEYSIG', 'REVKEYSIG'):  # pragma: no cover\n            # signed with expired or revoked key\n            self.valid = False\n            self.key_id, self.username = value.split(None, 1)\n            if key == 'EXPKEYSIG':\n                self.key_status = 'signing key has expired'\n            else:\n                self.key_status = 'signing key was revoked'\n            self.status = self.key_status\n            update_sig_info(status=self.status, keyid=self.key_id)\n            self.problems.append({'status': self.status, 'keyid': self.key_id})\n        elif key in ('UNEXPECTED', 'FAILURE'):  # pragma: no cover\n            self.valid = False\n            if key == 'UNEXPECTED':\n                self.status = 'unexpected data'\n            else:\n                # N.B. there might be other reasons. For example, if an output\n                # file can't  be created - /dev/null/foo will lead to a\n                # \"not a directory\" error, but which is not sent as a status\n                # message with the [GNUPG:] prefix. Similarly if you try to\n                # write to \"/etc/foo\" as a non-root user, a \"permission denied\"\n                # error will be sent as a non-status message.\n                message = 'error - %s' % value\n                operation, code = value.rsplit(' ', 1)\n                if code.isdigit():\n                    code = int(code) & 0xFFFFFF  # lose the error source\n                    if self.gpg.error_map and code in self.gpg.error_map:\n                        message = '%s: %s' % (operation, self.gpg.error_map[code])\n                    else:\n                        system_error = bool(code & 0x8000)\n                        code = code & 0x7FFF\n                        if system_error:\n                            mapping = self.GPG_SYSTEM_ERROR_CODES\n                        else:\n                            mapping = self.GPG_ERROR_CODES\n                        if code in mapping:\n                            message = '%s: %s' % (operation, mapping[code])\n                if not self.status:\n                    self.status = message\n        elif key == 'NODATA':  # pragma: no cover\n            # See issue GH-191\n            self.valid = False\n            self.status = 'signature expected but not found'\n        elif key in ('DECRYPTION_INFO', 'PLAINTEXT', 'PLAINTEXT_LENGTH', 'BEGIN_SIGNING', 'KEY_CONSIDERED'):\n            pass\n        elif key in ('NEWSIG', ):\n            # Only sent in gpg2. Clear any signature ID, to be set by a following SIG_ID\n            self.signature_id = None\n        else:  # pragma: no cover\n            logger.debug('message ignored: %r, %r', key, value)\n\n\nclass ImportResult(StatusHandler):\n    \"\"\"\n    This class handles status messages during key import.\n    \"\"\"\n\n    counts = '''count no_user_id imported imported_rsa unchanged n_uids n_subk n_sigs n_revoc sec_read sec_imported\n            sec_dups not_imported'''.split()\n\n    returncode = None\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.results = []\n        self.fingerprints = []\n        for result in self.counts:\n            setattr(self, result, 0)\n\n    def __nonzero__(self):\n        return bool(not self.not_imported and self.fingerprints)\n\n    __bool__ = __nonzero__\n\n    ok_reason = {\n        '0': 'Not actually changed',\n        '1': 'Entirely new key',\n        '2': 'New user IDs',\n        '4': 'New signatures',\n        '8': 'New subkeys',\n        '16': 'Contains private key',\n    }\n\n    problem_reason = {\n        '0': 'No specific reason given',\n        '1': 'Invalid Certificate',\n        '2': 'Issuer Certificate missing',\n        '3': 'Certificate Chain too long',\n        '4': 'Error storing certificate',\n    }\n\n    def handle_status(self, key, value):\n        if key in ('WARNING', 'ERROR'):  # pragma: no cover\n            logger.warning('potential problem: %s: %s', key, value)\n        elif key in ('IMPORTED', 'KEY_CONSIDERED'):\n            # this duplicates info we already see in import_ok & import_problem\n            pass\n        elif key == 'NODATA':  # pragma: no cover\n            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'No valid data found'})\n        elif key == 'IMPORT_OK':\n            reason, fingerprint = value.split()\n            reasons = []\n            for code, text in list(self.ok_reason.items()):\n                if int(reason) | int(code) == int(reason):\n                    reasons.append(text)\n            reasontext = '\\n'.join(reasons) + '\\n'\n            self.results.append({'fingerprint': fingerprint, 'ok': reason, 'text': reasontext})\n            self.fingerprints.append(fingerprint)\n        elif key == 'IMPORT_PROBLEM':  # pragma: no cover\n            try:\n                reason, fingerprint = value.split()\n            except Exception:\n                reason = value\n                fingerprint = '<unknown>'\n            self.results.append({'fingerprint': fingerprint, 'problem': reason, 'text': self.problem_reason[reason]})\n        elif key == 'IMPORT_RES':\n            import_res = value.split()\n            for i, count in enumerate(self.counts):\n                setattr(self, count, int(import_res[i]))\n        elif key == 'KEYEXPIRED':  # pragma: no cover\n            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Key expired'})\n        elif key == 'SIGEXPIRED':  # pragma: no cover\n            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Signature expired'})\n        elif key == 'FAILURE':  # pragma: no cover\n            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Other failure'})\n        else:  # pragma: no cover\n            logger.debug('message ignored: %s, %s', key, value)\n\n    def summary(self):\n        \"\"\"\n        Return a summary indicating how many keys were imported and how many were not imported.\n        \"\"\"\n        result = []\n        result.append('%d imported' % self.imported)\n        if self.not_imported:  # pragma: no cover\n            result.append('%d not imported' % self.not_imported)\n        return ', '.join(result)\n\n\nESCAPE_PATTERN = re.compile(r'\\\\x([0-9a-f][0-9a-f])', re.I)\nBASIC_ESCAPES = {\n    r'\\n': '\\n',\n    r'\\r': '\\r',\n    r'\\f': '\\f',\n    r'\\v': '\\v',\n    r'\\b': '\\b',\n    r'\\0': '\\0',\n}\n\n\nclass SendResult(StatusHandler):\n    \"\"\"\n    This class handles status messages during key sending.\n    \"\"\"\n\n    returncode = None\n\n    def handle_status(self, key, value):\n        logger.debug('SendResult: %s: %s', key, value)\n\n\ndef _set_fields(target, fieldnames, args):\n    for i, var in enumerate(fieldnames):\n        if i < len(args):\n            target[var] = args[i]\n        else:\n            target[var] = 'unavailable'\n\n\nclass SearchKeys(StatusHandler, list):\n    \"\"\"\n    This class handles status messages during key search.\n    \"\"\"\n\n    # Handle pub and uid (relating the latter to the former).\n    # Don't care about the rest\n\n    UID_INDEX = 1\n    FIELDS = 'type keyid algo length date expires'.split()\n    returncode = None\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.curkey = None\n        self.fingerprints = []\n        self.uids = []\n        self.uid_map = {}\n\n    def get_fields(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        result = {}\n        _set_fields(result, self.FIELDS, args)\n        result['uids'] = []\n        result['sigs'] = []\n        return result\n\n    def pub(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        self.curkey = curkey = self.get_fields(args)\n        self.append(curkey)\n\n    def uid(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        uid = args[self.UID_INDEX]\n        uid = ESCAPE_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), uid)\n        for k, v in BASIC_ESCAPES.items():\n            uid = uid.replace(k, v)\n        self.curkey['uids'].append(uid)\n        self.uids.append(uid)\n        uid_data = {}\n        self.uid_map[uid] = uid_data\n        for fn, fv in zip(self.FIELDS, args):\n            uid_data[fn] = fv\n\n    def handle_status(self, key, value):  # pragma: no cover\n        pass\n\n\nclass ListKeys(SearchKeys):\n    \"\"\"\n    This class handles status messages during listing keys and signatures.\n\n    Handle pub and uid (relating the latter to the former).\n\n    We don't care about (info from GnuPG DETAILS file):\n\n    crt = X.509 certificate\n    crs = X.509 certificate and private key available\n    uat = user attribute (same as user id except for field 10).\n    sig = signature\n    rev = revocation signature\n    pkd = public key data (special field format, see below)\n    grp = reserved for gpgsm\n    rvk = revocation key\n    \"\"\"\n\n    UID_INDEX = 9\n    FIELDS = ('type trust length algo keyid date expires dummy ownertrust uid sig'\n              ' cap issuer flag token hash curve compliance updated origin keygrip').split()\n\n    def __init__(self, gpg):\n        super(ListKeys, self).__init__(gpg)\n        self.in_subkey = False\n        self.key_map = {}\n\n    def key(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        self.curkey = curkey = self.get_fields(args)\n        if curkey['uid']:  # pragma: no cover\n            curkey['uids'].append(curkey['uid'])\n        del curkey['uid']\n        curkey['subkeys'] = []\n        self.append(curkey)\n        self.in_subkey = False\n\n    pub = sec = key\n\n    def fpr(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        fp = args[9]\n        if fp in self.key_map and self.gpg.check_fingerprint_collisions:  # pragma: no cover\n            raise ValueError('Unexpected fingerprint collision: %s' % fp)\n        if not self.in_subkey:\n            self.curkey['fingerprint'] = fp\n            self.fingerprints.append(fp)\n            self.key_map[fp] = self.curkey\n        else:\n            self.curkey['subkeys'][-1][2] = fp\n            self.key_map[fp] = self.curkey\n\n    def grp(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        grp = args[9]\n        if not self.in_subkey:\n            self.curkey['keygrip'] = grp\n        else:\n            self.curkey['subkeys'][-1][3] = grp\n\n    def _collect_subkey_info(self, curkey, args):\n        info_map = curkey.setdefault('subkey_info', {})\n        info = {}\n        _set_fields(info, self.FIELDS, args)\n        info_map[args[4]] = info\n\n    def sub(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        # See issue #81. We create a dict with more information about\n        # subkeys, but for backward compatibility reason, have to add it in\n        # as a separate entry 'subkey_info'\n        subkey = [args[4], args[11], None, None]  # keyid, type, fp, grp\n        self.curkey['subkeys'].append(subkey)\n        self._collect_subkey_info(self.curkey, args)\n        self.in_subkey = True\n\n    def ssb(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        subkey = [args[4], None, None, None]  # keyid, type, fp, grp\n        self.curkey['subkeys'].append(subkey)\n        self._collect_subkey_info(self.curkey, args)\n        self.in_subkey = True\n\n    def sig(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        # keyid, uid, sigclass\n        self.curkey['sigs'].append((args[4], args[9], args[10]))\n\n\nclass ScanKeys(ListKeys):\n    \"\"\"\n    This class handles status messages during scanning keys.\n    \"\"\"\n\n    def sub(self, args):\n        \"\"\"\n        Internal method used to update the instance from a `gpg` status message.\n        \"\"\"\n        # --with-fingerprint --with-colons somehow outputs fewer colons,\n        # use the last value args[-1] instead of args[11]\n        subkey = [args[4], args[-1], None, None]\n        self.curkey['subkeys'].append(subkey)\n        self._collect_subkey_info(self.curkey, args)\n        self.in_subkey = True\n\n\nclass TextHandler(object):\n\n    def _as_text(self):\n        return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)\n\n    if _py3k:\n        __str__ = _as_text\n    else:\n        __unicode__ = _as_text\n\n        def __str__(self):\n            return self.data\n\n\n_INVALID_KEY_REASONS = {\n    0: 'no specific reason given',\n    1: 'not found',\n    2: 'ambiguous specification',\n    3: 'wrong key usage',\n    4: 'key revoked',\n    5: 'key expired',\n    6: 'no crl known',\n    7: 'crl too old',\n    8: 'policy mismatch',\n    9: 'not a secret key',\n    10: 'key not trusted',\n    11: 'missing certificate',\n    12: 'missing issuer certificate',\n    13: 'key disabled',\n    14: 'syntax error in specification',\n}\n\n\ndef _determine_invalid_recipient_or_signer(s):  # pragma: no cover\n    parts = s.split()\n    if len(parts) >= 2:\n        code, ident = parts[:2]\n    else:\n        code = parts[0]\n        ident = '<no ident>'\n    unexpected = 'unexpected return code %r' % code\n    try:\n        key = int(code)\n        result = _INVALID_KEY_REASONS.get(key, unexpected)\n    except ValueError:\n        result = unexpected\n    return '%s:%s' % (result, ident)\n\n\nclass Crypt(Verify, TextHandler):\n    \"\"\"\n    This class handles status messages during encryption and decryption.\n    \"\"\"\n\n    def __init__(self, gpg):\n        Verify.__init__(self, gpg)\n        self.data = ''\n        self.ok = False\n        self.status = ''\n        self.status_detail = ''\n        self.key_id = None\n\n    def __nonzero__(self):\n        return bool(self.ok)\n\n    __bool__ = __nonzero__\n\n    def handle_status(self, key, value):\n        if key in ('WARNING', 'ERROR'):\n            logger.warning('potential problem: %s: %s', key, value)\n        elif key == 'NODATA':\n            if self.status not in ('decryption failed', ):\n                self.status = 'no data was provided'\n        elif key in ('NEED_PASSPHRASE', 'BAD_PASSPHRASE', 'GOOD_PASSPHRASE', 'MISSING_PASSPHRASE', 'KEY_NOT_CREATED',\n                     'NEED_PASSPHRASE_PIN'):  # pragma: no cover\n            self.status = key.replace('_', ' ').lower()\n        elif key == 'DECRYPTION_FAILED':  # pragma: no cover\n            if self.status != 'no secret key':  # don't overwrite more useful message\n                self.status = 'decryption failed'\n        elif key == 'NEED_PASSPHRASE_SYM':\n            self.status = 'need symmetric passphrase'\n        elif key == 'BEGIN_DECRYPTION':\n            if self.status != 'no secret key':  # don't overwrite more useful message\n                self.status = 'decryption incomplete'\n        elif key == 'BEGIN_ENCRYPTION':\n            self.status = 'encryption incomplete'\n        elif key == 'DECRYPTION_OKAY':\n            self.status = 'decryption ok'\n            self.ok = True\n        elif key == 'END_ENCRYPTION':\n            self.status = 'encryption ok'\n            self.ok = True\n        elif key == 'INV_RECP':  # pragma: no cover\n            if not self.status:\n                self.status = 'invalid recipient'\n            else:\n                self.status = 'invalid recipient: %s' % self.status\n            self.status_detail = _determine_invalid_recipient_or_signer(value)\n        elif key == 'KEYEXPIRED':  # pragma: no cover\n            self.status = 'key expired'\n        elif key == 'SIG_CREATED':  # pragma: no cover\n            self.status = 'sig created'\n        elif key == 'SIGEXPIRED':  # pragma: no cover\n            self.status = 'sig expired'\n        elif key == 'ENC_TO':  # pragma: no cover\n            # ENC_TO <long_keyid> <keytype> <keylength>\n            self.key_id = value.split(' ', 1)[0]\n        elif key in ('USERID_HINT', 'GOODMDC', 'END_DECRYPTION', 'CARDCTRL', 'BADMDC', 'SC_OP_FAILURE',\n                     'SC_OP_SUCCESS', 'PINENTRY_LAUNCHED'):\n            pass\n        else:\n            Verify.handle_status(self, key, value)\n\n\nclass GenKey(StatusHandler):\n    \"\"\"\n    This class handles status messages during key generation.\n    \"\"\"\n\n    returncode = None\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.type = None\n        self.fingerprint = ''\n        self.status = None\n\n    def __nonzero__(self):  # pragma: no cover\n        return bool(self.fingerprint)\n\n    __bool__ = __nonzero__\n\n    def __str__(self):  # pragma: no cover\n        return self.fingerprint\n\n    def handle_status(self, key, value):\n        if key in ('WARNING', 'ERROR'):  # pragma: no cover\n            logger.warning('potential problem: %s: %s', key, value)\n        elif key == 'KEY_CREATED':\n            parts = value.split()\n            (self.type, self.fingerprint) = parts[:2]\n            self.status = 'ok'\n        elif key == 'KEY_NOT_CREATED':\n            self.status = key.replace('_', ' ').lower()\n        elif key in ('PROGRESS', 'GOOD_PASSPHRASE'):  # pragma: no cover\n            pass\n        else:  # pragma: no cover\n            logger.debug('message ignored: %s, %s', key, value)\n\n\nclass AddSubkey(StatusHandler):\n    \"\"\"\n    This class handles status messages during subkey addition.\n    \"\"\"\n\n    returncode = None\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.type = None\n        self.fingerprint = ''\n        self.status = None\n\n    def __nonzero__(self):  # pragma: no cover\n        return bool(self.fingerprint)\n\n    __bool__ = __nonzero__\n\n    def __str__(self):\n        return self.fingerprint\n\n    def handle_status(self, key, value):\n        if key in ('WARNING', 'ERROR'):  # pragma: no cover\n            logger.warning('potential problem: %s: %s', key, value)\n        elif key == 'KEY_CREATED':\n            (self.type, self.fingerprint) = value.split()\n            self.status = 'ok'\n        else:  # pragma: no cover\n            logger.debug('message ignored: %s, %s', key, value)\n\n\nclass ExportResult(GenKey):\n    \"\"\"\n    This class handles status messages during key export.\n    \"\"\"\n\n    # For now, just use an existing class to base it on - if needed, we\n    # can override handle_status for more specific message handling.\n\n    def handle_status(self, key, value):\n        if key in ('EXPORTED', 'EXPORT_RES'):\n            pass\n        else:\n            super(ExportResult, self).handle_status(key, value)\n\n\nclass DeleteResult(StatusHandler):\n    \"\"\"\n    This class handles status messages during key deletion.\n    \"\"\"\n\n    returncode = None\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.status = 'ok'\n\n    def __str__(self):\n        return self.status\n\n    problem_reason = {\n        '1': 'No such key',\n        '2': 'Must delete secret key first',\n        '3': 'Ambiguous specification',\n    }\n\n    def handle_status(self, key, value):\n        if key == 'DELETE_PROBLEM':  # pragma: no cover\n            self.status = self.problem_reason.get(value, 'Unknown error: %r' % value)\n        else:  # pragma: no cover\n            logger.debug('message ignored: %s, %s', key, value)\n\n    def __nonzero__(self):  # pragma: no cover\n        return self.status == 'ok'\n\n    __bool__ = __nonzero__\n\n\nclass TrustResult(DeleteResult):\n    \"\"\"\n    This class handles status messages during key trust setting.\n    \"\"\"\n    pass\n\n\nclass Sign(StatusHandler, TextHandler):\n    \"\"\"\n    This class handles status messages during signing.\n    \"\"\"\n\n    returncode = None\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.type = None\n        self.hash_algo = None\n        self.fingerprint = None\n        self.status = None\n        self.status_detail = None\n        self.key_id = None\n        self.username = None\n\n    def __nonzero__(self):\n        return self.fingerprint is not None\n\n    __bool__ = __nonzero__\n\n    def handle_status(self, key, value):\n        if key in ('WARNING', 'ERROR', 'FAILURE'):  # pragma: no cover\n            logger.warning('potential problem: %s: %s', key, value)\n        elif key in ('KEYEXPIRED', 'SIGEXPIRED'):  # pragma: no cover\n            self.status = 'key expired'\n        elif key == 'KEYREVOKED':  # pragma: no cover\n            self.status = 'key revoked'\n        elif key == 'SIG_CREATED':\n            (self.type, algo, self.hash_algo, cls, self.timestamp, self.fingerprint) = value.split()\n            self.status = 'signature created'\n        elif key == 'USERID_HINT':  # pragma: no cover\n            self.key_id, self.username = value.split(' ', 1)\n        elif key == 'BAD_PASSPHRASE':  # pragma: no cover\n            self.status = 'bad passphrase'\n        elif key in ('INV_SGNR', 'INV_RECP'):  # pragma: no cover\n            # INV_RECP is returned in older versions\n            if not self.status:\n                self.status = 'invalid signer'\n            else:\n                self.status = 'invalid signer: %s' % self.status\n            self.status_detail = _determine_invalid_recipient_or_signer(value)\n        elif key in ('NEED_PASSPHRASE', 'GOOD_PASSPHRASE', 'BEGIN_SIGNING'):\n            pass\n        else:  # pragma: no cover\n            logger.debug('message ignored: %s, %s', key, value)\n\n\nclass AutoLocateKey(StatusHandler):\n    \"\"\"\n    This class handles status messages during key auto-locating.\n    fingerprint: str\n    key_length: int\n    created_at: date\n    email: str\n    email_real_name: str\n    \"\"\"\n\n    def __init__(self, gpg):\n        StatusHandler.__init__(self, gpg)\n        self.fingerprint = None\n        self.type = None\n        self.created_at = None\n        self.email = None\n        self.email_real_name = None\n\n    def handle_status(self, key, value):\n        if key == \"IMPORTED\":\n            _, email, display_name = value.split()\n\n            self.email = email\n            self.email_real_name = display_name[1:-1]\n        elif key == \"KEY_CONSIDERED\":\n            self.fingerprint = value.strip().split()[0]\n\n    def pub(self, args):\n        \"\"\"\n        Internal method to handle the 'pub' status message.\n        `pub` message contains the fingerprint of the public key, its type and its creation date.\n        \"\"\"\n        pass\n\n    def uid(self, args):\n        self.created_at = datetime.fromtimestamp(int(args[5]))\n        raw_email_content = args[9]\n        email, real_name = parseaddr(raw_email_content)\n        self.email = email\n        self.email_real_name = real_name\n\n    def sub(self, args):\n        self.key_length = int(args[2])\n\n    def fpr(self, args):\n        # Only store the first fingerprint\n        self.fingerprint = self.fingerprint or args[9]\n\n\nVERSION_RE = re.compile(r'\\bcfg:version:(\\d+(\\.\\d+)*)'.encode('ascii'))\nHEX_DIGITS_RE = re.compile(r'[0-9a-f]+$', re.I)\nPUBLIC_KEY_RE = re.compile(r'gpg: public key is (\\w+)')\n\n\nclass GPG(object):\n    \"\"\"\n    This class provides a high-level programmatic interface for `gpg`.\n    \"\"\"\n    error_map = None\n\n    decode_errors = 'strict'\n\n    buffer_size = 16384  # override in instance if needed\n\n    result_map = {\n        'crypt': Crypt,\n        'delete': DeleteResult,\n        'generate': GenKey,\n        'addSubkey': AddSubkey,\n        'import': ImportResult,\n        'send': SendResult,\n        'list': ListKeys,\n        'scan': ScanKeys,\n        'search': SearchKeys,\n        'sign': Sign,\n        'trust': TrustResult,\n        'verify': Verify,\n        'export': ExportResult,\n        'auto-locate-key': AutoLocateKey,\n    }\n    \"A map of GPG operations to result object types.\"\n\n    def __init__(self,\n                 gpgbinary='gpg',\n                 gnupghome=None,\n                 verbose=False,\n                 use_agent=False,\n                 keyring=None,\n                 options=None,\n                 secret_keyring=None,\n                 env=None):\n        \"\"\"Initialize a GPG process wrapper.\n\n        Args:\n            gpgbinary (str): A pathname for the GPG binary to use.\n\n            gnupghome (str): A pathname to where we can find the public and private keyrings. The default is\n                             whatever `gpg` defaults to.\n\n            keyring (str|list): The name of alternative keyring file to use, or a list of such keyring files. If\n                                specified, the default keyring is not used.\n\n            options (list): A list of additional options to pass to the GPG binary.\n\n            secret_keyring (str|list): The name of an alternative secret keyring file to use, or a list of such\n                                       keyring files.\n\n            env (dict): A dict of environment variables to be used for the GPG subprocess.\n        \"\"\"\n        self.gpgbinary = gpgbinary\n        self.gnupghome = gnupghome\n        self.env = env\n        # issue 112: fail if the specified value isn't a directory\n        if gnupghome and not os.path.isdir(gnupghome):\n            raise ValueError('gnupghome should be a directory (it isn\\'t): %s' % gnupghome)\n        if keyring:\n            # Allow passing a string or another iterable. Make it uniformly\n            # a list of keyring filenames\n            if isinstance(keyring, string_types):\n                keyring = [keyring]\n        self.keyring = keyring\n        if secret_keyring:  # pragma: no cover\n            # Allow passing a string or another iterable. Make it uniformly\n            # a list of keyring filenames\n            if isinstance(secret_keyring, string_types):\n                secret_keyring = [secret_keyring]\n        self.secret_keyring = secret_keyring\n        self.verbose = verbose\n        self.use_agent = use_agent\n        if isinstance(options, str):  # pragma: no cover\n            options = [options]\n        self.options = options\n        self.on_data = None  # or a callable - will be called with data chunks\n        # Changed in 0.3.7 to use Latin-1 encoding rather than\n        # locale.getpreferredencoding falling back to sys.stdin.encoding\n        # falling back to utf-8, because gpg itself uses latin-1 as the default\n        # encoding.\n        self.encoding = 'latin-1'\n        if gnupghome and not os.path.isdir(self.gnupghome):  # pragma: no cover\n            os.makedirs(self.gnupghome, 0o700)\n        try:\n            p = self._open_subprocess(['--list-config', '--with-colons'])\n        except OSError:\n            msg = 'Unable to run gpg (%s) - it may not be available.' % self.gpgbinary\n            logger.exception(msg)\n            raise OSError(msg)\n        result = self.result_map['verify'](self)  # any result will do for this\n        self._collect_output(p, result, stdin=p.stdin)\n        if p.returncode != 0:  # pragma: no cover\n            raise ValueError('Error invoking gpg: %s: %s' % (p.returncode, result.stderr))\n        m = VERSION_RE.search(result.data)\n        if not m:  # pragma: no cover\n            self.version = None\n        else:\n            dot = '.'.encode('ascii')\n            self.version = tuple([int(s) for s in m.groups()[0].split(dot)])\n\n        # See issue #97. It seems gpg allow duplicate keys in keyrings, so we\n        # can't be too strict.\n        self.check_fingerprint_collisions = False\n\n    def make_args(self, args, passphrase):\n        \"\"\"\n        Make a list of command line elements for GPG. The value of ``args``\n        will be appended. The ``passphrase`` argument needs to be True if\n        a passphrase will be sent to `gpg`, else False.\n\n        Args:\n            args (list[str]): A list of arguments.\n            passphrase (str): The passphrase to use.\n        \"\"\"\n        cmd = [self.gpgbinary, '--status-fd', '2', '--no-tty', '--no-verbose']\n        if 'DEBUG_IPC' in os.environ:  # pragma: no cover\n            cmd.extend(['--debug', 'ipc'])\n        if passphrase and hasattr(self, 'version'):\n            if self.version >= (2, 1):\n                cmd[1:1] = ['--pinentry-mode', 'loopback']\n        cmd.extend(['--fixed-list-mode', '--batch', '--with-colons'])\n        if self.gnupghome:\n            cmd.extend(['--homedir', no_quote(self.gnupghome)])\n        if self.keyring:\n            cmd.append('--no-default-keyring')\n            for fn in self.keyring:\n                cmd.extend(['--keyring', no_quote(fn)])\n        if self.secret_keyring:  # pragma: no cover\n            for fn in self.secret_keyring:\n                cmd.extend(['--secret-keyring', no_quote(fn)])\n        if passphrase:\n            cmd.extend(['--passphrase-fd', '0'])\n        if self.use_agent:  # pragma: no cover\n            cmd.append('--use-agent')\n        if self.options:\n            cmd.extend(self.options)\n        cmd.extend(args)\n        return cmd\n\n    def _open_subprocess(self, args, passphrase=False):\n        # Internal method: open a pipe to a GPG subprocess and return\n        # the file objects for communicating with it.\n\n        from subprocess import list2cmdline as debug_print\n\n        cmd = self.make_args(args, passphrase)\n        if self.verbose:  # pragma: no cover\n            print(debug_print(cmd))\n        if not STARTUPINFO:\n            si = None\n        else:  # pragma: no cover\n            si = STARTUPINFO()\n            si.dwFlags = STARTF_USESHOWWINDOW\n            si.wShowWindow = SW_HIDE\n        result = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE, startupinfo=si, env=self.env)\n        logger.debug('%s: %s', result.pid, debug_print(cmd))\n        return result\n\n    def _read_response(self, stream, result):\n        # Internal method: reads all the stderr output from GPG, taking notice\n        # only of lines that begin with the magic [GNUPG:] prefix.\n        #\n        # Calls methods on the response object for each valid token found,\n        # with the arg being the remainder of the status line.\n        lines = []\n        while True:\n            line = stream.readline()\n            if len(line) == 0:\n                break\n            lines.append(line)\n            line = line.rstrip()\n            if self.verbose:  # pragma: no cover\n                print(line)\n            logger.debug('%s', line)\n            if line[0:9] == '[GNUPG:] ':\n                # Chop off the prefix\n                line = line[9:]\n                L = line.split(None, 1)\n                keyword = L[0]\n                if len(L) > 1:\n                    value = L[1]\n                else:\n                    value = ''\n                result.handle_status(keyword, value)\n        result.stderr = ''.join(lines)\n\n    def _read_data(self, stream, result, on_data=None, buffer_size=1024):\n        # Read the contents of the file from GPG's stdout\n        assert buffer_size > 0\n        chunks = []\n        on_data_failure = None\n        while True:\n            data = stream.read(buffer_size)\n            if len(data) == 0:\n                if on_data:\n                    try:\n                        on_data(data)\n                    except Exception as e:\n                        if on_data_failure is None:\n                            on_data_failure = e\n                break\n            if log_everything:\n                logger.debug('chunk: %r' % data[:256])\n            append = True\n            if on_data:\n                try:\n                    on_data_result = on_data(data)\n                    append = on_data_result is not False\n                except Exception as e:\n                    if on_data_failure is None:\n                        on_data_failure = e\n            if append:\n                chunks.append(data)\n        if _py3k:\n            # Join using b'' or '', as appropriate\n            result.data = type(data)().join(chunks)\n        else:\n            result.data = ''.join(chunks)\n        if on_data_failure:\n            result.on_data_failure = on_data_failure\n\n    def _collect_output(self, process, result, writer=None, stdin=None):\n        \"\"\"\n        Drain the subprocesses output streams, writing the collected output to the result. If a writer thread (writing\n        to the subprocess) is given, make sure it's joined before returning. If a stdin stream is given, close it\n        before returning.\n        \"\"\"\n        stderr = codecs.getreader(self.encoding)(process.stderr)\n        rr = threading.Thread(target=self._read_response, args=(stderr, result))\n        rr.daemon = True\n        logger.debug('stderr reader: %r', rr)\n        rr.start()\n\n        stdout = process.stdout\n        dr = threading.Thread(target=self._read_data, args=(stdout, result, self.on_data, self.buffer_size))\n        dr.daemon = True\n        logger.debug('stdout reader: %r', dr)\n        dr.start()\n\n        dr.join()\n        rr.join()\n        if writer is not None:\n            writer.join(0.01)\n        process.wait()\n        result.returncode = rc = process.returncode\n        if rc != 0:\n            logger.warning('gpg returned a non-zero error code: %d', rc)\n        if stdin is not None:\n            try:\n                stdin.close()\n            except IOError:  # pragma: no cover\n                pass\n        stderr.close()\n        stdout.close()\n        return rc\n\n    def is_valid_file(self, fileobj):\n        \"\"\"\n        A simplistic check for a file-like object.\n\n        Args:\n            fileobj (object): The object to test.\n        Returns:\n            bool: ``True`` if it's a file-like object, else ``False``.\n        \"\"\"\n        return hasattr(fileobj, 'read')\n\n    def _get_fileobj(self, fileobj_or_path):\n        if self.is_valid_file(fileobj_or_path):\n            result = fileobj_or_path\n        elif not isinstance(fileobj_or_path, path_types):\n            raise TypeError('Not a valid file or path: %s' % fileobj_or_path)\n        elif not os.path.exists(fileobj_or_path):\n            raise ValueError('No such file: %s' % fileobj_or_path)\n        else:\n            result = open(fileobj_or_path, 'rb')\n        return result\n\n    def _handle_io(self, args, fileobj_or_path, result, passphrase=None, binary=False):\n        \"Handle a call to GPG - pass input data, collect output data\"\n        # Handle a basic data call - pass data to GPG, handle the output\n        # including status information. Garbage In, Garbage Out :)\n        fileobj = self._get_fileobj(fileobj_or_path)\n        writer = None  # See issue #237\n        try:\n            p = self._open_subprocess(args, passphrase is not None)\n            if not binary:  # pragma: no cover\n                stdin = codecs.getwriter(self.encoding)(p.stdin)\n            else:\n                stdin = p.stdin\n            if passphrase:\n                _write_passphrase(stdin, passphrase, self.encoding)\n            error_queue = Queue()\n            writer = _threaded_copy_data(fileobj, stdin, self.buffer_size, error_queue)\n            self._collect_output(p, result, writer, stdin)\n            try:\n                exc = error_queue.get_nowait()\n                # if we get here, that means an error occurred in the copying thread\n                raise exc\n            except Empty:\n                pass\n            return result\n        finally:\n            if writer:\n                writer.join(0.01)\n            if fileobj is not fileobj_or_path:\n                fileobj.close()\n\n    #\n    # SIGNATURE METHODS\n    #\n\n    def sign(self, message, **kwargs):\n        \"\"\"\n        Sign a message. This method delegates most of the work to the `sign_file()` method.\n\n        Args:\n            message (str|bytes): The data to sign.\n            kwargs (dict): Keyword arguments, which are passed to `sign_file()`:\n\n                * keyid (str): The key id of the signer.\n\n                * passphrase (str): The passphrase for the key.\n\n                * clearsign (bool): Whether to use clear signing.\n\n                * detach (bool): Whether to produce a detached signature.\n\n                * binary (bool): Whether to produce a binary signature.\n\n                * output (str): The path to write a detached signature to.\n\n                * extra_args (list[str]): Additional arguments to pass to `gpg`.\n        \"\"\"\n        f = _make_binary_stream(message, self.encoding)\n        result = self.sign_file(f, **kwargs)\n        f.close()\n        return result\n\n    def set_output_without_confirmation(self, args, output):\n        \"\"\"\n        If writing to a file which exists, avoid a confirmation message by\n        updating the *args* value in place to set the output path and avoid\n        any cpmfirmation prompt.\n\n        Args:\n            args (list[str]): A list of arguments.\n            output (str): The path to the outpur file.\n        \"\"\"\n        if os.path.exists(output):\n            # We need to avoid an overwrite confirmation message\n            args.extend(['--yes'])\n        args.extend(['--output', no_quote(output)])\n\n    def is_valid_passphrase(self, passphrase):\n        \"\"\"\n        Confirm that the passphrase doesn't contain newline-type characters - it is passed in a pipe to `gpg`,\n        and so not checking could lead to spoofing attacks by passing arbitrary text after passphrase and newline.\n\n        Args:\n            passphrase (str): The passphrase to test.\n\n        Returns:\n            bool: ``True`` if it's a valid passphrase, else ``False``.\n        \"\"\"\n        return ('\\n' not in passphrase and '\\r' not in passphrase and '\\x00' not in passphrase)\n\n    def sign_file(self,\n                  fileobj_or_path,\n                  keyid=None,\n                  passphrase=None,\n                  clearsign=True,\n                  detach=False,\n                  binary=False,\n                  output=None,\n                  extra_args=None):\n        \"\"\"\n        Sign data in a file or file-like object.\n\n        Args:\n            fileobj_or_path (str|file): The file or file-like object to sign.\n\n            keyid (str): The key id of the signer.\n\n            passphrase (str): The passphrase for the key.\n\n            clearsign (bool): Whether to use clear signing.\n\n            detach (bool): Whether to produce a detached signature.\n\n            binary (bool): Whether to produce a binary signature.\n\n            output (str): The path to write a detached signature to.\n\n            extra_args (list[str]): Additional arguments to pass to `gpg`.\n        \"\"\"\n        if passphrase and not self.is_valid_passphrase(passphrase):\n            raise ValueError('Invalid passphrase')\n        logger.debug('sign_file: %s', fileobj_or_path)\n        if binary:  # pragma: no cover\n            args = ['-s']\n        else:\n            args = ['-sa']\n        # You can't specify detach-sign and clearsign together: gpg ignores\n        # the detach-sign in that case.\n        if detach:\n            args.append('--detach-sign')\n        elif clearsign:\n            args.append('--clearsign')\n        if keyid:\n            args.extend(['--default-key', no_quote(keyid)])\n        if output:  # pragma: no cover\n            # write the output to a file with the specified name\n            self.set_output_without_confirmation(args, output)\n\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        result = self.result_map['sign'](self)\n        # We could use _handle_io here except for the fact that if the\n        # passphrase is bad, gpg bails and you can't write the message.\n        fileobj = self._get_fileobj(fileobj_or_path)\n        p = self._open_subprocess(args, passphrase is not None)\n        writer = None\n        try:\n            stdin = p.stdin\n            if passphrase:\n                _write_passphrase(stdin, passphrase, self.encoding)\n            error_queue = Queue()\n            writer = _threaded_copy_data(fileobj, stdin, self.buffer_size, error_queue)\n            try:\n                exc = error_queue.get_nowait()\n                # if we get here, that means an error occurred in the copying thread\n                raise exc\n            except Empty:\n                pass\n        except IOError:  # pragma: no cover\n            logging.exception('error writing message')\n        finally:\n            if writer:\n                writer.join(0.01)\n            if fileobj is not fileobj_or_path:\n                fileobj.close()\n        self._collect_output(p, result, writer, stdin)\n        return result\n\n    def verify(self, data, **kwargs):\n        \"\"\"\n        Verify the signature on the contents of the string *data*. This method delegates most of the work to\n        `verify_file()`.\n\n        Args:\n            data (str|bytes): The data to verify.\n            kwargs (dict): Keyword arguments, which are passed to `verify_file()`:\n\n                * fileobj_or_path (str|file): A path to a signature, or a file-like object containing one.\n\n                * data_filename (str): If the signature is a detached one, the path to the data that was signed.\n\n                * close_file (bool): If a file-like object is passed in, whether to close it.\n\n                * extra_args (list[str]): Additional arguments to pass to `gpg`.\n        \"\"\"\n        f = _make_binary_stream(data, self.encoding)\n        result = self.verify_file(f, **kwargs)\n        f.close()\n        return result\n\n    def verify_file(self, fileobj_or_path, data_filename=None, close_file=True, extra_args=None):\n        \"\"\"\n        Verify a signature.\n\n        Args:\n            fileobj_or_path (str|file): A path to a signature, or a file-like object containing one.\n\n            data_filename (str): If the signature is a detached one, the path to the data that was signed.\n\n            close_file (bool): If a file-like object is passed in, whether to close it.\n\n            extra_args (list[str]): Additional arguments to pass to `gpg`.\n        \"\"\"\n        logger.debug('verify_file: %r, %r', fileobj_or_path, data_filename)\n        result = self.result_map['verify'](self)\n        args = ['--verify']\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        if data_filename is None:\n            self._handle_io(args, fileobj_or_path, result, binary=True)\n        else:\n            logger.debug('Handling detached verification')\n            import tempfile\n            fileobj = self._get_fileobj(fileobj_or_path)\n            fd, fn = tempfile.mkstemp(prefix='pygpg-')\n            s = fileobj.read()\n            if fileobj is not fileobj_or_path:\n                fileobj.close()\n            elif close_file:\n                fileobj_or_path.close()\n            logger.debug('Wrote to temp file: %r', s)\n            os.write(fd, s)\n            os.close(fd)\n            args.append(no_quote(fn))\n            args.append(no_quote(data_filename))\n            try:\n                p = self._open_subprocess(args)\n                self._collect_output(p, result, stdin=p.stdin)\n            finally:\n                os.remove(fn)\n        return result\n\n    def verify_data(self, sig_filename, data, extra_args=None):\n        \"\"\"\n        Verify the signature in sig_filename against data in memory\n\n        Args:\n            sig_filename (str): The path to a signature.\n\n            data (str|bytes): The data to be verified.\n\n            extra_args (list[str]): Additional arguments to pass to `gpg`.\n        \"\"\"\n        logger.debug('verify_data: %r, %r ...', sig_filename, data[:16])\n        result = self.result_map['verify'](self)\n        args = ['--verify']\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        args.extend([no_quote(sig_filename), '-'])\n        stream = _make_memory_stream(data)\n        self._handle_io(args, stream, result, binary=True)\n        return result\n\n    #\n    # KEY MANAGEMENT\n    #\n\n    def import_keys(self, key_data, extra_args=None, passphrase=None):\n        \"\"\"\n        Import the key_data into our keyring.\n\n        Args:\n            key_data (str|bytes): The key data to import.\n\n            passphrase (str): The passphrase to use.\n\n            extra_args (list[str]): Additional arguments to pass to `gpg`.\n        \"\"\"\n        result = self.result_map['import'](self)\n        logger.debug('import_keys: %r', key_data[:256])\n        data = _make_binary_stream(key_data, self.encoding)\n        args = ['--import']\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        self._handle_io(args, data, result, passphrase=passphrase, binary=True)\n        logger.debug('import_keys result: %r', result.__dict__)\n        data.close()\n        return result\n\n    def import_keys_file(self, key_path, **kwargs):\n        \"\"\"\n        Import the key data in key_path into our keyring.\n\n        Args:\n            key_path (str): A path to the key data to be imported.\n        \"\"\"\n        with open(key_path, 'rb') as f:\n            return self.import_keys(f.read(), **kwargs)\n\n    def recv_keys(self, keyserver, *keyids, **kwargs):\n        \"\"\"\n        Import one or more keys from a keyserver.\n\n        Args:\n            keyserver (str): The key server hostname.\n\n            keyids (str): A list of key ids to receive.\n        \"\"\"\n        result = self.result_map['import'](self)\n        logger.debug('recv_keys: %r', keyids)\n        data = _make_binary_stream('', self.encoding)\n        args = ['--keyserver', no_quote(keyserver)]\n        if 'extra_args' in kwargs:  # pragma: no cover\n            args.extend(kwargs['extra_args'])\n        args.append('--recv-keys')\n        args.extend([no_quote(k) for k in keyids])\n        self._handle_io(args, data, result, binary=True)\n        logger.debug('recv_keys result: %r', result.__dict__)\n        data.close()\n        return result\n\n    # This function isn't exercised by tests, to avoid polluting external\n    # key servers with test keys\n    def send_keys(self, keyserver, *keyids, **kwargs):  # pragma: no cover\n        \"\"\"\n        Send one or more keys to a keyserver.\n\n        Args:\n            keyserver (str): The key server hostname.\n\n            keyids (list[str]): A list of key ids to send.\n        \"\"\"\n\n        # Note: it's not practical to test this function without sending\n        # arbitrary data to live keyservers.\n\n        result = self.result_map['send'](self)\n        logger.debug('send_keys: %r', keyids)\n        data = _make_binary_stream('', self.encoding)\n        args = ['--keyserver', no_quote(keyserver)]\n        if 'extra_args' in kwargs:\n            args.extend(kwargs['extra_args'])\n        args.append('--send-keys')\n        args.extend([no_quote(k) for k in keyids])\n        self._handle_io(args, data, result, binary=True)\n        logger.debug('send_keys result: %r', result.__dict__)\n        data.close()\n        return result\n\n    def delete_keys(self, fingerprints, secret=False, passphrase=None, expect_passphrase=True, exclamation_mode=False):\n        \"\"\"\n        Delete the indicated keys.\n\n        Args:\n            fingerprints (str|list[str]): The keys to delete.\n\n            secret (bool): Whether to delete secret keys.\n\n            passphrase (str): The passphrase to use.\n\n            expect_passphrase (bool): Whether a passphrase is expected.\n\n            exclamation_mode (bool): If specified, a `'!'` is appended to each fingerprint. This deletes only a subkey\n                                     or an entire key, depending on what the fingerprint refers to.\n\n        .. note:: Passphrases\n\n           Since GnuPG 2.1, you can't delete secret keys without providing a passphrase. However, if you're expecting\n           the passphrase to go to `gpg` via pinentry, you should specify expect_passphrase=False. (It's only checked\n           for GnuPG >= 2.1).\n        \"\"\"\n        if passphrase and not self.is_valid_passphrase(passphrase):  # pragma: no cover\n            raise ValueError('Invalid passphrase')\n        which = 'key'\n        if secret:  # pragma: no cover\n            if self.version >= (2, 1) and passphrase is None and expect_passphrase:\n                raise ValueError('For GnuPG >= 2.1, deleting secret keys '\n                                 'needs a passphrase to be provided')\n            which = 'secret-key'\n        if _is_sequence(fingerprints):  # pragma: no cover\n            fingerprints = [no_quote(s) for s in fingerprints]\n        else:\n            fingerprints = [no_quote(fingerprints)]\n\n        if exclamation_mode:\n            fingerprints = [f + '!' for f in fingerprints]\n\n        args = ['--delete-%s' % which]\n        if secret and self.version >= (2, 1):\n            args.insert(0, '--yes')\n        args.extend(fingerprints)\n        result = self.result_map['delete'](self)\n        if not secret or self.version < (2, 1):\n            p = self._open_subprocess(args)\n            self._collect_output(p, result, stdin=p.stdin)\n        else:\n            # Need to send in a passphrase.\n            f = _make_binary_stream('', self.encoding)\n            try:\n                self._handle_io(args, f, result, passphrase=passphrase, binary=True)\n            finally:\n                f.close()\n        return result\n\n    def export_keys(self,\n                    keyids,\n                    secret=False,\n                    armor=True,\n                    minimal=False,\n                    passphrase=None,\n                    expect_passphrase=True,\n                    output=None):\n        \"\"\"\n        Export the indicated keys. A 'keyid' is anything `gpg` accepts.\n\n        Args:\n            keyids (str|list[str]): A single keyid or a list of them.\n\n            secret (bool): Whether to export secret keys.\n\n            armor (bool): Whether to ASCII-armor the output.\n\n            minimal (bool): Whether to pass `--export-options export-minimal` to `gpg`.\n\n            passphrase (str): The passphrase to use.\n\n            expect_passphrase (bool): Whether a passphrase is expected.\n\n            output (str): If specified, the path to write the exported key(s) to.\n\n        .. note:: Passphrases\n\n           Since GnuPG 2.1, you can't export secret keys without providing a passphrase. However, if you're expecting\n           the passphrase to go to `gpg` via pinentry, you should specify expect_passphrase=False. (It's only checked\n           for GnuPG >= 2.1).\n        \"\"\"\n        if passphrase and not self.is_valid_passphrase(passphrase):  # pragma: no cover\n            raise ValueError('Invalid passphrase')\n        which = ''\n        if secret:\n            which = '-secret-key'\n            if self.version >= (2, 1) and passphrase is None and expect_passphrase:  # pragma: no cover\n                raise ValueError('For GnuPG >= 2.1, exporting secret keys '\n                                 'needs a passphrase to be provided')\n        if _is_sequence(keyids):\n            keyids = [no_quote(k) for k in keyids]\n        else:\n            keyids = [no_quote(keyids)]\n        args = ['--export%s' % which]\n        if armor:\n            args.append('--armor')\n        if minimal:  # pragma: no cover\n            args.extend(['--export-options', 'export-minimal'])\n        if output:  # pragma: no cover\n            # write the output to a file with the specified name\n            self.set_output_without_confirmation(args, output)\n        args.extend(keyids)\n        # gpg --export produces no status-fd output; stdout will be\n        # empty in case of failure\n        result = self.result_map['export'](self)\n        if not secret or self.version < (2, 1):\n            p = self._open_subprocess(args)\n            self._collect_output(p, result, stdin=p.stdin)\n        else:\n            # Need to send in a passphrase.\n            f = _make_binary_stream('', self.encoding)\n            try:\n                self._handle_io(args, f, result, passphrase=passphrase, binary=True)\n            finally:\n                f.close()\n        logger.debug('export_keys result[:100]: %r', result.data[:100])\n        # Issue #49: Return bytes if armor not specified, else text\n        result = result.data\n        if armor:\n            result = result.decode(self.encoding, self.decode_errors)\n        return result\n\n    def _decode_result(self, result):\n        lines = result.data.decode(self.encoding, self.decode_errors).splitlines()\n        valid_keywords = 'pub uid sec fpr sub ssb sig grp'.split()\n        for line in lines:\n            if self.verbose:  # pragma: no cover\n                print(line)\n            logger.debug('line: %r', line.rstrip())\n            if not line:  # pragma: no cover\n                break\n            L = line.strip().split(':')\n            if not L:  # pragma: no cover\n                continue\n            keyword = L[0]\n            if keyword in valid_keywords:\n                getattr(result, keyword)(L)\n        return result\n\n    def _get_list_output(self, p, kind):\n        # Get the response information\n        result = self.result_map[kind](self)\n        self._collect_output(p, result, stdin=p.stdin)\n        return self._decode_result(result)\n\n    def list_keys(self, secret=False, keys=None, sigs=False):\n        \"\"\"\n        List the keys currently in the keyring.\n\n        Args:\n            secret (bool): Whether to list secret keys.\n\n            keys (str|list[str]): A list of key ids to match.\n\n            sigs (bool): Whether to include signature information.\n\n        Returns:\n            list[dict]: A list of dictionaries with key information.\n        \"\"\"\n\n        if secret:\n            which = 'secret-keys'\n        else:\n            which = 'sigs' if sigs else 'keys'\n        args = ['--list-%s' % which, '--fingerprint', '--fingerprint']  # get subkey FPs, too\n\n        if self.version >= (2, 1):\n            args.append('--with-keygrip')\n\n        if keys:\n            if isinstance(keys, string_types):\n                keys = [keys]\n            args.extend(keys)\n        p = self._open_subprocess(args)\n        result = self._get_list_output(p, 'list')\n        # Fix up subkey_info with fingerprint and grip values\n        for key in result:\n            # import pdb; pdb.set_trace()\n            subkeys = key['subkeys']\n            subkey_info = key.get('subkey_info')\n            if subkey_info:\n                for sk in subkeys:\n                    skid, capability, fp, grp = sk\n                    d = subkey_info[skid]\n                    d['capability'] = capability\n                    d['fingerprint'] = fp\n                    d['keygrip'] = grp\n        return result\n\n    def scan_keys(self, filename):\n        \"\"\"\n        List details of an ascii armored or binary key file without first importing it to the local keyring.\n\n        Args:\n            filename (str): The path to the file containing the key(s).\n\n        .. warning:: Warning:\n            Care is needed. The function works on modern GnuPG by running:\n\n                $ gpg --dry-run --import-options import-show --import filename\n\n            On older versions, it does the *much* riskier:\n\n                $ gpg --with-fingerprint --with-colons filename\n        \"\"\"\n        if self.version >= (2, 1):\n            args = ['--dry-run', '--import-options', 'import-show', '--import']\n        else:\n            logger.warning('Trying to list packets, but if the file is not a '\n                           'keyring, might accidentally decrypt')\n            args = ['--with-fingerprint', '--with-colons', '--fixed-list-mode']\n        args.append(no_quote(filename))\n        p = self._open_subprocess(args)\n        return self._get_list_output(p, 'scan')\n\n    def scan_keys_mem(self, key_data):\n        \"\"\"\n        List details of an ascii armored or binary key without first importing it to the local keyring.\n\n        Args:\n            key_data (str|bytes): The key data to import.\n\n        .. warning:: Warning:\n            Care is needed. The function works on modern GnuPG by running:\n\n                $ gpg --dry-run --import-options import-show --import filename\n\n            On older versions, it does the *much* riskier:\n\n                $ gpg --with-fingerprint --with-colons filename\n        \"\"\"\n        result = self.result_map['scan'](self)\n        logger.debug('scan_keys: %r', key_data[:256])\n        data = _make_binary_stream(key_data, self.encoding)\n        if self.version >= (2, 1):\n            args = ['--dry-run', '--import-options', 'import-show', '--import']\n        else:\n            logger.warning('Trying to list packets, but if the file is not a '\n                           'keyring, might accidentally decrypt')\n            args = ['--with-fingerprint', '--with-colons', '--fixed-list-mode']\n        self._handle_io(args, data, result, binary=True)\n        logger.debug('scan_keys result: %r', result.__dict__)\n        data.close()\n        return self._decode_result(result)\n\n    def search_keys(self, query, keyserver='pgp.mit.edu', extra_args=None):\n        \"\"\"\n        search a keyserver by query (using the `--search-keys` option).\n\n        Args:\n            query(str): The query to use.\n\n            keyserver (str): The key server hostname.\n\n            extra_args (list[str]): Additional arguments to pass to `gpg`.\n        \"\"\"\n        query = query.strip()\n        if HEX_DIGITS_RE.match(query):\n            query = '0x' + query\n        args = ['--fingerprint', '--keyserver', no_quote(keyserver)]\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        args.extend(['--search-keys', no_quote(query)])\n        p = self._open_subprocess(args)\n\n        # Get the response information\n        result = self.result_map['search'](self)\n        self._collect_output(p, result, stdin=p.stdin)\n        lines = result.data.decode(self.encoding, self.decode_errors).splitlines()\n        valid_keywords = ['pub', 'uid']\n        for line in lines:\n            if self.verbose:  # pragma: no cover\n                print(line)\n            logger.debug('line: %r', line.rstrip())\n            if not line:  # sometimes get blank lines on Windows\n                continue\n            L = line.strip().split(':')\n            if not L:  # pragma: no cover\n                continue\n            keyword = L[0]\n            if keyword in valid_keywords:\n                getattr(result, keyword)(L)\n        return result\n\n    def auto_locate_key(self, email, mechanisms=None, **kwargs):\n        \"\"\"\n        Auto locate a public key by `email`.\n\n        Args:\n            email (str): The email address to search for.\n            mechanisms (list[str]): A list of mechanisms to use. Valid mechanisms can be found\n            here https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html\n            under \"--auto-key-locate\". Default: ['wkd', 'ntds', 'ldap', 'cert', 'dane', 'local']\n        \"\"\"\n        mechanisms = mechanisms or ['wkd', 'ntds', 'ldap', 'cert', 'dane', 'local']\n\n        args = ['--auto-key-locate', ','.join(mechanisms), '--locate-keys', email]\n\n        result = self.result_map['auto-locate-key'](self)\n\n        if 'extra_args' in kwargs:\n            args.extend(kwargs['extra_args'])\n\n        process = self._open_subprocess(args)\n        self._collect_output(process, result, stdin=process.stdin)\n        self._decode_result(result)\n        return result\n\n    def gen_key(self, input):\n        \"\"\"\n        Generate a key; you might use `gen_key_input()` to create the input.\n\n        Args:\n            input (str): The input to the key creation operation.\n        \"\"\"\n        args = ['--gen-key']\n        result = self.result_map['generate'](self)\n        f = _make_binary_stream(input, self.encoding)\n        self._handle_io(args, f, result, binary=True)\n        f.close()\n        return result\n\n    def gen_key_input(self, **kwargs):\n        \"\"\"\n        Generate `--gen-key` input  (see `gpg` documentation in DETAILS).\n\n        Args:\n            kwargs (dict): A list of keyword arguments.\n        Returns:\n            str: A string suitable for passing to the `gen_key()` method.\n        \"\"\"\n\n        parms = {}\n        no_protection = kwargs.pop('no_protection', False)\n        for key, val in list(kwargs.items()):\n            key = key.replace('_', '-').title()\n            if str(val).strip():  # skip empty strings\n                parms[key] = val\n        parms.setdefault('Key-Type', 'RSA')\n        if 'key_curve' not in kwargs:\n            parms.setdefault('Key-Length', 2048)\n        parms.setdefault('Name-Real', 'Autogenerated Key')\n        logname = (os.environ.get('LOGNAME') or os.environ.get('USERNAME') or 'unspecified')\n        hostname = socket.gethostname()\n        parms.setdefault('Name-Email', '%s@%s' % (logname.replace(' ', '_'), hostname))\n        out = 'Key-Type: %s\\n' % parms.pop('Key-Type')\n        for key, val in list(parms.items()):\n            out += '%s: %s\\n' % (key, val)\n        if no_protection:  # pragma: no cover\n            out += '%no-protection\\n'\n        out += '%commit\\n'\n        return out\n\n        # Key-Type: RSA\n        # Key-Length: 1024\n        # Name-Real: ISdlink Server on %s\n        # Name-Comment: Created by %s\n        # Name-Email: isdlink@%s\n        # Expire-Date: 0\n        # %commit\n        #\n        #\n        # Key-Type: DSA\n        # Key-Length: 1024\n        # Subkey-Type: ELG-E\n        # Subkey-Length: 1024\n        # Name-Real: Joe Tester\n        # Name-Comment: with stupid passphrase\n        # Name-Email: joe@foo.bar\n        # Expire-Date: 0\n        # Passphrase: abc\n        # %pubring foo.pub\n        # %secring foo.sec\n        # %commit\n\n    def add_subkey(self, master_key, master_passphrase=None, algorithm='rsa', usage='encrypt', expire='-'):\n        \"\"\"\n        Add subkeys to a master key,\n\n        Args:\n            master_key (str): The master key.\n\n            master_passphrase (str): The passphrase for the master key.\n\n            algorithm (str): The key algorithm to use.\n\n            usage (str): The desired uses for the subkey.\n\n            expire (str): The expiration date of the subkey.\n        \"\"\"\n        if self.version[0] < 2:\n            raise NotImplementedError('Not available in GnuPG 1.x')\n        if not master_key:  # pragma: no cover\n            raise ValueError('No master key fingerprint specified')\n\n        if master_passphrase and not self.is_valid_passphrase(master_passphrase):  # pragma: no cover\n            raise ValueError('Invalid passphrase')\n\n        args = ['--quick-add-key', master_key, algorithm, usage, str(expire)]\n\n        result = self.result_map['addSubkey'](self)\n\n        f = _make_binary_stream('', self.encoding)\n        self._handle_io(args, f, result, passphrase=master_passphrase, binary=True)\n        return result\n\n    def quick_sign_key(self, certifier_fingerprint, recipient_fingerprint, certifier_passphrase=None):\n        \"\"\"\n        Certify a key using quick-sign-key function.\n\n        Args:\n            certifier_fingerprint (str): The fingerprint for the certifying key.\n\n            recipient_fingerprint (str): The fingerprint of the key being signed.\n\n            certifier_passphrase (str): The passphrase for the certifing key.\n        \"\"\"\n        if self.version[0] < 2:\n            raise NotImplementedError('Not available in GnuPG 1.x')\n        if not certifier_fingerprint:  # pragma: no cover\n            raise ValueError('No certifier key fingerprint specified')\n        if not recipient_fingerprint:  # pragma: no cover\n            raise ValueError('No recipient key fingerprint specified')\n        if certifier_passphrase and not self.is_valid_passphrase(certifier_passphrase):  # pragma: no cover\n            raise ValueError('Invalid passphrase')\n\n        args = ['--local-user', certifier_fingerprint, '--quick-sign-key', recipient_fingerprint]\n\n        result = self.result_map['sign'](self)\n\n        f = _make_binary_stream('', self.encoding)\n        self._handle_io(args, f, result, passphrase=certifier_passphrase, binary=True)\n        return result\n\n    #\n    # ENCRYPTION\n    #\n\n    def encrypt_file(self,\n                     fileobj_or_path,\n                     recipients,\n                     hidden_recipients=None,\n                     sign=None,\n                     always_trust=False,\n                     passphrase=None,\n                     armor=True,\n                     output=None,\n                     symmetric=False,\n                     extra_args=None):\n        \"\"\"\n        Encrypt data in a file or file-like object.\n\n        Args:\n            fileobj_or_path (str|file): A path to a file or a file-like object containing the data to be encrypted.\n\n            recipients (str|list): A key id of a recipient of the encrypted data, or a list of such key ids.\n\n            hidden_recipients (str|list): A key id of a hidden recipient of the encrypted data, or a list of such key ids.\n\n            sign (str): If specified, the key id of a signer to sign the encrypted data.\n\n            always_trust (bool): Whether to always trust keys.\n\n            passphrase (str): The passphrase to use for a signature.\n\n            armor (bool): Whether to ASCII-armor the output.\n\n            output (str): A path to write the encrypted output to.\n\n            symmetric (bool): Whether to use symmetric encryption,\n\n            extra_args (list[str]): A list of additional arguments to pass to `gpg`.\n        \"\"\"\n        if passphrase and not self.is_valid_passphrase(passphrase):\n            raise ValueError('Invalid passphrase')\n        args = ['--encrypt']\n        if symmetric:\n            # can't be False or None - could be True or a cipher algo value\n            # such as AES256\n            args = ['--symmetric']\n            if symmetric is not True:\n                args.extend(['--cipher-algo', no_quote(symmetric)])\n            # else use the default, currently CAST5\n        else:\n            if not recipients and not hidden_recipients:\n                raise ValueError('No recipients or hidden recipients specified with '\n                                 'asymmetric encryption')\n            if recipients:\n                if not _is_sequence(recipients):\n                    recipients = (recipients, )\n                for recipient in recipients:\n                    args.extend(['--recipient', no_quote(recipient)])\n\n            if hidden_recipients:\n                if not _is_sequence(hidden_recipients):\n                    hidden_recipients = (hidden_recipients, )\n                for hidden_recipient in hidden_recipients:\n                    args.extend(['--hidden-recipient', no_quote(hidden_recipient)])\n        if armor:  # create ascii-armored output - False for binary output\n            args.append('--armor')\n        if output:  # pragma: no cover\n            # write the output to a file with the specified name\n            self.set_output_without_confirmation(args, output)\n        if sign is True:  # pragma: no cover\n            args.append('--sign')\n        elif sign:  # pragma: no cover\n            args.extend(['--sign', '--default-key', no_quote(sign)])\n        if always_trust:  # pragma: no cover\n            args.extend(['--trust-model', 'always'])\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        result = self.result_map['crypt'](self)\n        self._handle_io(args, fileobj_or_path, result, passphrase=passphrase, binary=True)\n        logger.debug('encrypt result[:100]: %r', result.data[:100])\n        return result\n\n    def encrypt(self, data, recipients, **kwargs):\n        \"\"\"\n        Encrypt the message contained in the string *data* for *recipients*. This method delegates most of the work to\n        `encrypt_file()`.\n\n        Args:\n            data (str|bytes): The data to encrypt.\n\n            recipients (str|list[str]): A key id of a recipient of the encrypted data, or a list of such key ids.\n\n            kwargs (dict): Keyword arguments, which are passed to `encrypt_file()`:\n                * sign (str): If specified, the key id of a signer to sign the encrypted data.\n\n                * always_trust (bool): Whether to always trust keys.\n\n                * passphrase (str): The passphrase to use for a signature.\n\n                * armor (bool): Whether to ASCII-armor the output.\n\n                * output (str): A path to write the encrypted output to.\n\n                * symmetric (bool): Whether to use symmetric encryption,\n\n                * extra_args (list[str]): A list of additional arguments to pass to `gpg`.\n        \"\"\"\n        data = _make_binary_stream(data, self.encoding)\n        result = self.encrypt_file(data, recipients, **kwargs)\n        data.close()\n        return result\n\n    def decrypt(self, message, **kwargs):\n        \"\"\"\n        Decrypt the data in *message*. This method delegates most of the work to\n        `decrypt_file()`.\n\n        Args:\n            message (str|bytes): The data to decrypt. A default key will be used for decryption.\n\n            kwargs (dict): Keyword arguments, which are passed to `decrypt_file()`:\n\n                * always_trust: Whether to always trust keys.\n\n                * passphrase (str): The passphrase to use.\n\n                * output (str): If specified, the path to write the decrypted data to.\n\n                * extra_args (list[str]): A list of extra arguments to pass to `gpg`.\n        \"\"\"\n        data = _make_binary_stream(message, self.encoding)\n        result = self.decrypt_file(data, **kwargs)\n        data.close()\n        return result\n\n    def decrypt_file(self, fileobj_or_path, always_trust=False, passphrase=None, output=None, extra_args=None):\n        \"\"\"\n        Decrypt data in a file or file-like object.\n\n        Args:\n            fileobj_or_path (str|file): A path to a file or a file-like object containing the data to be decrypted.\n\n            always_trust: Whether to always trust keys.\n\n            passphrase (str): The passphrase to use.\n\n            output (str): If specified, the path to write the decrypted data to.\n\n            extra_args (list[str]): A list of extra arguments to pass to `gpg`.\n        \"\"\"\n        if passphrase and not self.is_valid_passphrase(passphrase):\n            raise ValueError('Invalid passphrase')\n        args = ['--decrypt']\n        if output:  # pragma: no cover\n            # write the output to a file with the specified name\n            self.set_output_without_confirmation(args, output)\n        if always_trust:  # pragma: no cover\n            args.extend(['--trust-model', 'always'])\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        result = self.result_map['crypt'](self)\n        self._handle_io(args, fileobj_or_path, result, passphrase, binary=True)\n        # logger.debug('decrypt result[:100]: %r', result.data[:100])\n        return result\n\n    def get_recipients(self, message, **kwargs):\n        \"\"\" Get the list of recipients for an encrypted message. This method delegates most of the work to\n        `get_recipients_file()`.\n\n        Args:\n            message (str|bytes): The encrypted message.\n\n            kwargs (dict): Keyword arguments, which are passed to `get_recipients_file()`:\n\n                * extra_args (list[str]): A list of extra arguments to pass to `gpg`.\n        \"\"\"\n        data = _make_binary_stream(message, self.encoding)\n        result = self.get_recipients_file(data, **kwargs)\n        data.close()\n        return result\n\n    def get_recipients_file(self, fileobj_or_path, extra_args=None):\n        \"\"\"\n        Get the list of recipients for an encrypted message in a file or file-like object.\n\n        Args:\n            fileobj_or_path (str|file): A path to a file or file-like object containing the encrypted data.\n\n            extra_args (list[str]): A list of extra arguments to pass to `gpg`.\n        \"\"\"\n        args = ['--decrypt', '--list-only', '-v']\n        if extra_args:  # pragma: no cover\n            args.extend(extra_args)\n        result = self.result_map['crypt'](self)\n        self._handle_io(args, fileobj_or_path, result, binary=True)\n        ids = []\n        for m in PUBLIC_KEY_RE.finditer(result.stderr):\n            ids.append(m.group(1))\n        return ids\n\n    def trust_keys(self, fingerprints, trustlevel):\n        \"\"\"\n        Set the trust level for one or more keys.\n\n        Args:\n            fingerprints (str|list[str]): A key id for which to set the trust level, or a list of such key ids.\n\n            trustlevel (str): The trust level. This is one of the following.\n\n                                  * ``'TRUST_EXPIRED'``\n                                  * ``'TRUST_UNDEFINED'``\n                                  * ``'TRUST_NEVER'``\n                                  * ``'TRUST_MARGINAL'``\n                                  * ``'TRUST_FULLY'``\n                                  * ``'TRUST_ULTIMATE'``\n        \"\"\"\n        levels = Verify.TRUST_LEVELS\n        if trustlevel not in levels:\n            poss = ', '.join(sorted(levels))\n            raise ValueError('Invalid trust level: \"%s\" (must be one of %s)' % (trustlevel, poss))\n        trustlevel = levels[trustlevel] + 1\n        import tempfile\n        try:\n            fd, fn = tempfile.mkstemp(prefix='pygpg-')\n            lines = []\n            if isinstance(fingerprints, string_types):\n                fingerprints = [fingerprints]\n            for f in fingerprints:\n                lines.append('%s:%s:' % (f, trustlevel))\n            # The trailing newline is required!\n            s = os.linesep.join(lines) + os.linesep\n            logger.debug('writing ownertrust info: %s', s)\n            os.write(fd, s.encode(self.encoding))\n            os.close(fd)\n            result = self.result_map['trust'](self)\n            p = self._open_subprocess(['--import-ownertrust', fn])\n            self._collect_output(p, result, stdin=p.stdin)\n            if p.returncode != 0:\n                raise ValueError('gpg returned an error - return code %d' % p.returncode)\n        finally:\n            os.remove(fn)\n        return result\n"
  },
  {
    "path": "messages.json",
    "content": "{\n  \"0000\": \"Success\",\n  \"0001\": \"General error\",\n  \"0002\": \"Unknown packet\",\n  \"0003\": \"Unknown version in packet\",\n  \"0004\": \"Invalid public key algorithm\",\n  \"0005\": \"Invalid digest algorithm\",\n  \"0006\": \"Bad public key\",\n  \"0007\": \"Bad secret key\",\n  \"0008\": \"Bad signature\",\n  \"0009\": \"No public key\",\n  \"000A\": \"Checksum error\",\n  \"000B\": \"Bad passphrase\",\n  \"000C\": \"Invalid cipher algorithm\",\n  \"000D\": \"Cannot open keyring\",\n  \"000E\": \"Invalid packet\",\n  \"000F\": \"Invalid armor\",\n  \"0010\": \"No user ID\",\n  \"0011\": \"No secret key\",\n  \"0012\": \"Wrong secret key used\",\n  \"0013\": \"Bad session key\",\n  \"0014\": \"Unknown compression algorithm\",\n  \"0015\": \"Number is not prime\",\n  \"0016\": \"Invalid encoding method\",\n  \"0017\": \"Invalid encryption scheme\",\n  \"0018\": \"Invalid signature scheme\",\n  \"0019\": \"Invalid attribute\",\n  \"001A\": \"No value\",\n  \"001B\": \"Not found\",\n  \"001C\": \"Value not found\",\n  \"001D\": \"Syntax error\",\n  \"001E\": \"Bad MPI value\",\n  \"001F\": \"Invalid passphrase\",\n  \"0020\": \"Invalid signature class\",\n  \"0021\": \"Resources exhausted\",\n  \"0022\": \"Invalid keyring\",\n  \"0023\": \"Trust DB error\",\n  \"0024\": \"Bad certificate\",\n  \"0025\": \"Invalid user ID\",\n  \"0026\": \"Unexpected error\",\n  \"0027\": \"Time conflict\",\n  \"0028\": \"Keyserver error\",\n  \"0029\": \"Wrong public key algorithm\",\n  \"002A\": \"Tribute to D. A.\",\n  \"002B\": \"Weak encryption key\",\n  \"002C\": \"Invalid key length\",\n  \"002D\": \"Invalid argument\",\n  \"002E\": \"Syntax error in URI\",\n  \"002F\": \"Invalid URI\",\n  \"0030\": \"Network error\",\n  \"0031\": \"Unknown host\",\n  \"0032\": \"Selftest failed\",\n  \"0033\": \"Data not encrypted\",\n  \"0034\": \"Data not processed\",\n  \"0035\": \"Unusable public key\",\n  \"0036\": \"Unusable secret key\",\n  \"0037\": \"Invalid value\",\n  \"0038\": \"Bad certificate chain\",\n  \"0039\": \"Missing certificate\",\n  \"003A\": \"No data\",\n  \"003B\": \"Bug\",\n  \"003C\": \"Not supported\",\n  \"003D\": \"Invalid operation code\",\n  \"003E\": \"Timeout\",\n  \"003F\": \"Internal error\",\n  \"0040\": \"EOF (gcrypt)\",\n  \"0041\": \"Invalid object\",\n  \"0042\": \"Provided object is too short\",\n  \"0043\": \"Provided object is too large\",\n  \"0044\": \"Missing item in object\",\n  \"0045\": \"Not implemented\",\n  \"0046\": \"Conflicting use\",\n  \"0047\": \"Invalid cipher mode\",\n  \"0048\": \"Invalid flag\",\n  \"0049\": \"Invalid handle\",\n  \"004A\": \"Result truncated\",\n  \"004B\": \"Incomplete line\",\n  \"004C\": \"Invalid response\",\n  \"004D\": \"No agent running\",\n  \"004E\": \"Agent error\",\n  \"004F\": \"Invalid data\",\n  \"0050\": \"Unspecific Assuan server fault\",\n  \"0051\": \"General Assuan error\",\n  \"0052\": \"Invalid session key\",\n  \"0053\": \"Invalid S-expression\",\n  \"0054\": \"Unsupported algorithm\",\n  \"0055\": \"No pinentry\",\n  \"0056\": \"pinentry error\",\n  \"0057\": \"Bad PIN\",\n  \"0058\": \"Invalid name\",\n  \"0059\": \"Bad data\",\n  \"005A\": \"Invalid parameter\",\n  \"005B\": \"Wrong card\",\n  \"005C\": \"No dirmngr\",\n  \"005D\": \"dirmngr error\",\n  \"005E\": \"Certificate revoked\",\n  \"005F\": \"No CRL known\",\n  \"0060\": \"CRL too old\",\n  \"0061\": \"Line too long\",\n  \"0062\": \"Not trusted\",\n  \"0063\": \"Operation cancelled\",\n  \"0064\": \"Bad CA certificate\",\n  \"0065\": \"Certificate expired\",\n  \"0066\": \"Certificate too young\",\n  \"0067\": \"Unsupported certificate\",\n  \"0068\": \"Unknown S-expression\",\n  \"0069\": \"Unsupported protection\",\n  \"006A\": \"Corrupted protection\",\n  \"006B\": \"Ambiguous name\",\n  \"006C\": \"Card error\",\n  \"006D\": \"Card reset required\",\n  \"006E\": \"Card removed\",\n  \"006F\": \"Invalid card\",\n  \"0070\": \"Card not present\",\n  \"0071\": \"No PKCS15 application\",\n  \"0072\": \"Not confirmed\",\n  \"0073\": \"Configuration error\",\n  \"0074\": \"No policy match\",\n  \"0075\": \"Invalid index\",\n  \"0076\": \"Invalid ID\",\n  \"0077\": \"No SmartCard daemon\",\n  \"0078\": \"SmartCard daemon error\",\n  \"0079\": \"Unsupported protocol\",\n  \"007A\": \"Bad PIN method\",\n  \"007B\": \"Card not initialized\",\n  \"007C\": \"Unsupported operation\",\n  \"007D\": \"Wrong key usage\",\n  \"007E\": \"Nothing found\",\n  \"007F\": \"Wrong blob type\",\n  \"0080\": \"Missing value\",\n  \"0081\": \"Hardware problem\",\n  \"0082\": \"PIN blocked\",\n  \"0083\": \"Conditions of use not satisfied\",\n  \"0084\": \"PINs are not synced\",\n  \"0085\": \"Invalid CRL\",\n  \"0086\": \"BER error\",\n  \"0087\": \"Invalid BER\",\n  \"0088\": \"Element not found\",\n  \"0089\": \"Identifier not found\",\n  \"008A\": \"Invalid tag\",\n  \"008B\": \"Invalid length\",\n  \"008C\": \"Invalid key info\",\n  \"008D\": \"Unexpected tag\",\n  \"008E\": \"Not DER encoded\",\n  \"008F\": \"No CMS object\",\n  \"0090\": \"Invalid CMS object\",\n  \"0091\": \"Unknown CMS object\",\n  \"0092\": \"Unsupported CMS object\",\n  \"0093\": \"Unsupported encoding\",\n  \"0094\": \"Unsupported CMS version\",\n  \"0095\": \"Unknown algorithm\",\n  \"0096\": \"Invalid crypto engine\",\n  \"0097\": \"Public key not trusted\",\n  \"0098\": \"Decryption failed\",\n  \"0099\": \"Key expired\",\n  \"009A\": \"Signature expired\",\n  \"009B\": \"Encoding problem\",\n  \"009C\": \"Invalid state\",\n  \"009D\": \"Duplicated value\",\n  \"009E\": \"Missing action\",\n  \"009F\": \"ASN.1 module not found\",\n  \"00A0\": \"Invalid OID string\",\n  \"00A1\": \"Invalid time\",\n  \"00A2\": \"Invalid CRL object\",\n  \"00A3\": \"Unsupported CRL version\",\n  \"00A4\": \"Invalid certificate object\",\n  \"00A5\": \"Unknown name\",\n  \"00A6\": \"A locale function failed\",\n  \"00A7\": \"Not locked\",\n  \"00A8\": \"Protocol violation\",\n  \"00A9\": \"Invalid MAC\",\n  \"00AA\": \"Invalid request\",\n  \"00AB\": \"Unknown extension\",\n  \"00AC\": \"Unknown critical extension\",\n  \"00AD\": \"Locked\",\n  \"00AE\": \"Unknown option\",\n  \"00AF\": \"Unknown command\",\n  \"00B0\": \"Not operational\",\n  \"00B1\": \"No passphrase given\",\n  \"00B2\": \"No PIN given\",\n  \"00B3\": \"Not enabled\",\n  \"00B4\": \"No crypto engine\",\n  \"00B5\": \"Missing key\",\n  \"00B6\": \"Too many objects\",\n  \"00B7\": \"Limit reached\",\n  \"00B8\": \"Not initialized\",\n  \"00B9\": \"Missing issuer certificate\",\n  \"00BA\": \"No keyserver available\",\n  \"00BB\": \"Invalid elliptic curve\",\n  \"00BC\": \"Unknown elliptic curve\",\n  \"00BD\": \"Duplicated key\",\n  \"00BE\": \"Ambiguous result\",\n  \"00BF\": \"No crypto context\",\n  \"00C0\": \"Wrong crypto context\",\n  \"00C1\": \"Bad crypto context\",\n  \"00C2\": \"Conflict in the crypto context\",\n  \"00C3\": \"Broken public key\",\n  \"00C4\": \"Broken secret key\",\n  \"00C5\": \"Invalid MAC algorithm\",\n  \"00C6\": \"Operation fully cancelled\",\n  \"00C7\": \"Operation not yet finished\",\n  \"00C8\": \"Buffer too short\",\n  \"00C9\": \"Invalid length specifier in S-expression\",\n  \"00CA\": \"String too long in S-expression\",\n  \"00CB\": \"Unmatched parentheses in S-expression\",\n  \"00CC\": \"S-expression not canonical\",\n  \"00CD\": \"Bad character in S-expression\",\n  \"00CE\": \"Bad quotation in S-expression\",\n  \"00CF\": \"Zero prefix in S-expression\",\n  \"00D0\": \"Nested display hints in S-expression\",\n  \"00D1\": \"Unmatched display hints\",\n  \"00D2\": \"Unexpected reserved punctuation in S-expression\",\n  \"00D3\": \"Bad hexadecimal character in S-expression\",\n  \"00D4\": \"Odd hexadecimal numbers in S-expression\",\n  \"00D5\": \"Bad octal character in S-expression\",\n  \"00D9\": \"All subkeys are expired or revoked\",\n  \"00DA\": \"Database is corrupted\",\n  \"00DB\": \"Server indicated a failure\",\n  \"00DC\": \"No name\",\n  \"00DD\": \"No key\",\n  \"00DE\": \"Legacy key\",\n  \"00DF\": \"Request too short\",\n  \"00E0\": \"Request too long\",\n  \"00E1\": \"Object is in termination state\",\n  \"00E2\": \"No certificate chain\",\n  \"00E3\": \"Certificate is too large\",\n  \"00E4\": \"Invalid record\",\n  \"00E5\": \"The MAC does not verify\",\n  \"00E6\": \"Unexpected message\",\n  \"00E7\": \"Compression or decompression failed\",\n  \"00E8\": \"A counter would wrap\",\n  \"00E9\": \"Fatal alert message received\",\n  \"00EA\": \"No cipher algorithm\",\n  \"00EB\": \"Missing client certificate\",\n  \"00EC\": \"Close notification received\",\n  \"00ED\": \"Ticket expired\",\n  \"00EE\": \"Bad ticket\",\n  \"00EF\": \"Unknown identity\",\n  \"00F0\": \"Bad certificate message in handshake\",\n  \"00F1\": \"Bad certificate request message in handshake\",\n  \"00F2\": \"Bad certificate verify message in handshake\",\n  \"00F3\": \"Bad change cipher message in handshake\",\n  \"00F4\": \"Bad client hello message in handshake\",\n  \"00F5\": \"Bad server hello message in handshake\",\n  \"00F6\": \"Bad server hello done message in handshake\",\n  \"00F7\": \"Bad finished message in handshake\",\n  \"00F8\": \"Bad server key exchange message in handshake\",\n  \"00F9\": \"Bad client key exchange message in handshake\",\n  \"00FA\": \"Bogus string\",\n  \"00FB\": \"Forbidden\",\n  \"00FC\": \"Key disabled\",\n  \"00FD\": \"Not possible with a card based key\",\n  \"00FE\": \"Invalid lock object\",\n  \"00FF\": \"True\",\n  \"0100\": \"False\",\n  \"0101\": \"General IPC error\",\n  \"0102\": \"IPC accept call failed\",\n  \"0103\": \"IPC connect call failed\",\n  \"0104\": \"Invalid IPC response\",\n  \"0105\": \"Invalid value passed to IPC\",\n  \"0106\": \"Incomplete line passed to IPC\",\n  \"0107\": \"Line passed to IPC too long\",\n  \"0108\": \"Nested IPC commands\",\n  \"0109\": \"No data callback in IPC\",\n  \"010A\": \"No inquire callback in IPC\",\n  \"010B\": \"Not an IPC server\",\n  \"010C\": \"Not an IPC client\",\n  \"010D\": \"Problem starting IPC server\",\n  \"010E\": \"IPC read error\",\n  \"010F\": \"IPC write error\",\n  \"0111\": \"Too much data for IPC layer\",\n  \"0112\": \"Unexpected IPC command\",\n  \"0113\": \"Unknown IPC command\",\n  \"0114\": \"IPC syntax error\",\n  \"0115\": \"IPC call has been cancelled\",\n  \"0116\": \"No input source for IPC\",\n  \"0117\": \"No output source for IPC\",\n  \"0118\": \"IPC parameter error\",\n  \"0119\": \"Unknown IPC inquire\",\n  \"012C\": \"Crypto engine too old\",\n  \"012D\": \"Screen or window too small\",\n  \"012E\": \"Screen or window too large\",\n  \"012F\": \"Required environment variable not set\",\n  \"0130\": \"User ID already exists\",\n  \"0131\": \"Name already exists\",\n  \"0132\": \"Duplicated name\",\n  \"0133\": \"Object is too young\",\n  \"0134\": \"Object is too old\",\n  \"0135\": \"Unknown flag\",\n  \"0136\": \"Invalid execution order\",\n  \"0137\": \"Already fetched\",\n  \"0138\": \"Try again later\",\n  \"0139\": \"Wrong name\",\n  \"013A\": \"Not authenticated\",\n  \"013B\": \"Bad authentication\",\n  \"013C\": \"No Keybox daemon running\",\n  \"013D\": \"Keybox daemon error\",\n  \"013E\": \"Service is not running\",\n  \"013F\": \"Service error\",\n  \"029A\": \"System bug detected\",\n  \"02C7\": \"Unknown DNS error\",\n  \"02C8\": \"Invalid DNS section\",\n  \"02C9\": \"Invalid textual address form\",\n  \"02CA\": \"Missing DNS query packet\",\n  \"02CB\": \"Missing DNS answer packet\",\n  \"02CC\": \"Connection closed in DNS\",\n  \"02CD\": \"Verification failed in DNS\",\n  \"02CE\": \"DNS Timeout\",\n  \"02D1\": \"General LDAP error\",\n  \"02D2\": \"General LDAP attribute error\",\n  \"02D3\": \"General LDAP name error\",\n  \"02D4\": \"General LDAP security error\",\n  \"02D5\": \"General LDAP service error\",\n  \"02D6\": \"General LDAP update error\",\n  \"02D7\": \"Experimental LDAP error code\",\n  \"02D8\": \"Private LDAP error code\",\n  \"02D9\": \"Other general LDAP error\",\n  \"02EE\": \"LDAP connecting failed (X)\",\n  \"02EF\": \"LDAP referral limit exceeded\",\n  \"02F0\": \"LDAP client loop\",\n  \"02F2\": \"No LDAP results returned\",\n  \"02F3\": \"LDAP control not found\",\n  \"02F4\": \"Not supported by LDAP\",\n  \"02F5\": \"LDAP connect error\",\n  \"02F6\": \"Out of memory in LDAP\",\n  \"02F7\": \"Bad parameter to an LDAP routine\",\n  \"02F8\": \"User cancelled LDAP operation\",\n  \"02F9\": \"Bad LDAP search filter\",\n  \"02FA\": \"Unknown LDAP authentication method\",\n  \"02FB\": \"Timeout in LDAP\",\n  \"02FC\": \"LDAP decoding error\",\n  \"02FD\": \"LDAP encoding error\",\n  \"02FE\": \"LDAP local error\",\n  \"02FF\": \"Cannot contact LDAP server\",\n  \"0300\": \"LDAP success\",\n  \"0301\": \"LDAP operations error\",\n  \"0302\": \"LDAP protocol error\",\n  \"0303\": \"Time limit exceeded in LDAP\",\n  \"0304\": \"Size limit exceeded in LDAP\",\n  \"0305\": \"LDAP compare false\",\n  \"0306\": \"LDAP compare true\",\n  \"0307\": \"LDAP authentication method not supported\",\n  \"0308\": \"Strong(er) LDAP authentication required\",\n  \"0309\": \"Partial LDAP results+referral received\",\n  \"030A\": \"LDAP referral\",\n  \"030B\": \"Administrative LDAP limit exceeded\",\n  \"030C\": \"Critical LDAP extension is unavailable\",\n  \"030D\": \"Confidentiality required by LDAP\",\n  \"030E\": \"LDAP SASL bind in progress\",\n  \"0310\": \"No such LDAP attribute\",\n  \"0311\": \"Undefined LDAP attribute type\",\n  \"0312\": \"Inappropriate matching in LDAP\",\n  \"0313\": \"Constraint violation in LDAP\",\n  \"0314\": \"LDAP type or value exists\",\n  \"0315\": \"Invalid syntax in LDAP\",\n  \"0320\": \"No such LDAP object\",\n  \"0321\": \"LDAP alias problem\",\n  \"0322\": \"Invalid DN syntax in LDAP\",\n  \"0323\": \"LDAP entry is a leaf\",\n  \"0324\": \"LDAP alias dereferencing problem\",\n  \"032F\": \"LDAP proxy authorization failure (X)\",\n  \"0330\": \"Inappropriate LDAP authentication\",\n  \"0331\": \"Invalid LDAP credentials\",\n  \"0332\": \"Insufficient access for LDAP\",\n  \"0333\": \"LDAP server is busy\",\n  \"0334\": \"LDAP server is unavailable\",\n  \"0335\": \"LDAP server is unwilling to perform\",\n  \"0336\": \"Loop detected by LDAP\",\n  \"0340\": \"LDAP naming violation\",\n  \"0341\": \"LDAP object class violation\",\n  \"0342\": \"LDAP operation not allowed on non-leaf\",\n  \"0343\": \"LDAP operation not allowed on RDN\",\n  \"0344\": \"Already exists (LDAP)\",\n  \"0345\": \"Cannot modify LDAP object class\",\n  \"0346\": \"LDAP results too large\",\n  \"0347\": \"LDAP operation affects multiple DSAs\",\n  \"034C\": \"Virtual LDAP list view error\",\n  \"0350\": \"Other LDAP error\",\n  \"0371\": \"Resources exhausted in LCUP\",\n  \"0372\": \"Security violation in LCUP\",\n  \"0373\": \"Invalid data in LCUP\",\n  \"0374\": \"Unsupported scheme in LCUP\",\n  \"0375\": \"Reload required in LCUP\",\n  \"0376\": \"LDAP cancelled\",\n  \"0377\": \"No LDAP operation to cancel\",\n  \"0378\": \"Too late to cancel LDAP\",\n  \"0379\": \"Cannot cancel LDAP\",\n  \"037A\": \"LDAP assertion failed\",\n  \"037B\": \"Proxied authorization denied by LDAP\",\n  \"0400\": \"User defined error code 1\",\n  \"0401\": \"User defined error code 2\",\n  \"0402\": \"User defined error code 3\",\n  \"0403\": \"User defined error code 4\",\n  \"0404\": \"User defined error code 5\",\n  \"0405\": \"User defined error code 6\",\n  \"0406\": \"User defined error code 7\",\n  \"0407\": \"User defined error code 8\",\n  \"0408\": \"User defined error code 9\",\n  \"0409\": \"User defined error code 10\",\n  \"040A\": \"User defined error code 11\",\n  \"040B\": \"User defined error code 12\",\n  \"040C\": \"User defined error code 13\",\n  \"040D\": \"User defined error code 14\",\n  \"040E\": \"User defined error code 15\",\n  \"040F\": \"User defined error code 16\",\n  \"05DC\": \"SQL success\",\n  \"05DD\": \"SQL error\",\n  \"05DE\": \"Internal logic error in SQL library\",\n  \"05DF\": \"Access permission denied (SQL)\",\n  \"05E0\": \"SQL abort was requested\",\n  \"05E1\": \"SQL database file is locked\",\n  \"05E2\": \"An SQL table in the database is locked\",\n  \"05E3\": \"SQL library ran out of core\",\n  \"05E4\": \"Attempt to write a readonly SQL database\",\n  \"05E5\": \"SQL operation terminated by interrupt\",\n  \"05E6\": \"I/O error during SQL operation\",\n  \"05E7\": \"SQL database disk image is malformed\",\n  \"05E8\": \"Unknown opcode in SQL file control\",\n  \"05E9\": \"Insertion failed because SQL database is full\",\n  \"05EA\": \"Unable to open the SQL database file\",\n  \"05EB\": \"SQL database lock protocol error\",\n  \"05EC\": \"(internal SQL code: empty)\",\n  \"05ED\": \"SQL database schema changed\",\n  \"05EE\": \"String or blob exceeds size limit (SQL)\",\n  \"05EF\": \"SQL abort due to constraint violation\",\n  \"05F0\": \"Data type mismatch (SQL)\",\n  \"05F1\": \"SQL library used incorrectly\",\n  \"05F2\": \"SQL library uses unsupported OS features\",\n  \"05F3\": \"Authorization denied (SQL)\",\n  \"05F4\": \"(unused SQL code: format)\",\n  \"05F5\": \"SQL bind parameter out of range\",\n  \"05F6\": \"File opened that is not an SQL database file\",\n  \"05F7\": \"Notifications from SQL logger\",\n  \"05F8\": \"Warnings from SQL logger\",\n  \"0640\": \"SQL has another row ready\",\n  \"0641\": \"SQL has finished executing\",\n  \"3FFD\": \"System error w/o errno\",\n  \"3FFF\": \"End of file\",\n  \"8000\": \"Argument list too long\",\n  \"8001\": \"Permission denied\",\n  \"8002\": \"Address already in use\",\n  \"8003\": \"Cannot assign requested address\",\n  \"8004\": \"Advertise error\",\n  \"8005\": \"Address family not supported by protocol\",\n  \"8006\": \"Resource temporarily unavailable\",\n  \"8007\": \"Operation already in progress\",\n  \"800A\": \"Invalid exchange\",\n  \"800B\": \"Bad file descriptor\",\n  \"800C\": \"File descriptor in bad state\",\n  \"800D\": \"Bad message\",\n  \"800E\": \"Invalid request descriptor\",\n  \"8010\": \"Invalid request code\",\n  \"8011\": \"Invalid slot\",\n  \"8012\": \"Bad font file format\",\n  \"8013\": \"Device or resource busy\",\n  \"8014\": \"Operation canceled\",\n  \"8015\": \"No child processes\",\n  \"8016\": \"Channel number out of range\",\n  \"8017\": \"Communication error on send\",\n  \"8018\": \"Software caused connection abort\",\n  \"8019\": \"Connection refused\",\n  \"801A\": \"Connection reset by peer\",\n  \"801C\": \"Resource deadlock avoided\",\n  \"801D\": \"Resource deadlock avoided\",\n  \"801E\": \"Destination address required\",\n  \"8020\": \"Numerical argument out of domain\",\n  \"8021\": \"RFS specific error\",\n  \"8022\": \"Disk quota exceeded\",\n  \"8023\": \"File exists\",\n  \"8024\": \"Bad address\",\n  \"8025\": \"File too large\",\n  \"8029\": \"Host is down\",\n  \"802A\": \"No route to host\",\n  \"802B\": \"Identifier removed\",\n  \"802D\": \"Invalid or incomplete multibyte or wide character\",\n  \"802E\": \"Operation now in progress\",\n  \"802F\": \"Interrupted system call\",\n  \"8030\": \"Invalid argument\",\n  \"8031\": \"Input/output error\",\n  \"8032\": \"Transport endpoint is already connected\",\n  \"8033\": \"Is a directory\",\n  \"8034\": \"Is a named type file\",\n  \"8035\": \"Level 2 halted\",\n  \"8036\": \"Level 2 not synchronized\",\n  \"8037\": \"Level 3 halted\",\n  \"8038\": \"Level 3 reset\",\n  \"8039\": \"Can not access a needed shared library\",\n  \"803A\": \"Accessing a corrupted shared library\",\n  \"803B\": \"Cannot exec a shared library directly\",\n  \"803C\": \"Attempting to link in too many shared libraries\",\n  \"803D\": \".lib section in a.out corrupted\",\n  \"803E\": \"Link number out of range\",\n  \"803F\": \"Too many levels of symbolic links\",\n  \"8040\": \"Wrong medium type\",\n  \"8041\": \"Too many open files\",\n  \"8042\": \"Too many links\",\n  \"8043\": \"Message too long\",\n  \"8044\": \"Multihop attempted\",\n  \"8045\": \"File name too long\",\n  \"8046\": \"No XENIX semaphores available\",\n  \"8048\": \"Network is down\",\n  \"8049\": \"Network dropped connection on reset\",\n  \"804A\": \"Network is unreachable\",\n  \"804B\": \"Too many open files in system\",\n  \"804C\": \"No anode\",\n  \"804D\": \"No buffer space available\",\n  \"804E\": \"No CSI structure available\",\n  \"804F\": \"No data available\",\n  \"8050\": \"No such device\",\n  \"8051\": \"No such file or directory\",\n  \"8052\": \"Exec format error\",\n  \"8053\": \"No locks available\",\n  \"8054\": \"Link has been severed\",\n  \"8055\": \"No medium found\",\n  \"8056\": \"Cannot allocate memory\",\n  \"8057\": \"No message of desired type\",\n  \"8058\": \"Machine is not on the network\",\n  \"8059\": \"Package not installed\",\n  \"805A\": \"Protocol not available\",\n  \"805B\": \"No space left on device\",\n  \"805C\": \"Out of streams resources\",\n  \"805D\": \"Device not a stream\",\n  \"805E\": \"Function not implemented\",\n  \"805F\": \"Block device required\",\n  \"8060\": \"Transport endpoint is not connected\",\n  \"8061\": \"Not a directory\",\n  \"8062\": \"Directory not empty\",\n  \"8063\": \"Not a XENIX named type file\",\n  \"8064\": \"Socket operation on non-socket\",\n  \"8065\": \"Operation not supported\",\n  \"8066\": \"Inappropriate ioctl for device\",\n  \"8067\": \"Name not unique on network\",\n  \"8068\": \"No such device or address\",\n  \"8069\": \"Operation not supported\",\n  \"806A\": \"Value too large for defined data type\",\n  \"806B\": \"Operation not permitted\",\n  \"806C\": \"Protocol family not supported\",\n  \"806D\": \"Broken pipe\",\n  \"8072\": \"Protocol error\",\n  \"8073\": \"Protocol not supported\",\n  \"8074\": \"Protocol wrong type for socket\",\n  \"8075\": \"Numerical result out of range\",\n  \"8076\": \"Remote address changed\",\n  \"8077\": \"Object is remote\",\n  \"8078\": \"Remote I/O error\",\n  \"8079\": \"Interrupted system call should be restarted\",\n  \"807A\": \"Read-only file system\",\n  \"807C\": \"Cannot send after transport endpoint shutdown\",\n  \"807D\": \"Socket type not supported\",\n  \"807E\": \"Illegal seek\",\n  \"807F\": \"No such process\",\n  \"8080\": \"Srmount error\",\n  \"8081\": \"Stale file handle\",\n  \"8082\": \"Streams pipe error\",\n  \"8083\": \"Timer expired\",\n  \"8084\": \"Connection timed out\",\n  \"8085\": \"Too many references: cannot splice\",\n  \"8086\": \"Text file busy\",\n  \"8087\": \"Structure needs cleaning\",\n  \"8088\": \"Protocol driver not attached\",\n  \"8089\": \"Too many users\",\n  \"808A\": \"Resource temporarily unavailable\",\n  \"808B\": \"Invalid cross-device link\",\n  \"808C\": \"Exchange full\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"index-metadata\": {\n    \"extensions\": {\n      \"python.details\": {\n        \"classifiers\": [\n          \"Development Status :: 5 - Production/Stable\",\n          \"Intended Audience :: Developers\",\n          \"License :: OSI Approved :: BSD License\",\n          \"Programming Language :: Python\",\n          \"Programming Language :: Python :: 2\",\n          \"Programming Language :: Python :: 3\",\n          \"Programming Language :: Python :: 2.4\",\n          \"Programming Language :: Python :: 2.5\",\n          \"Programming Language :: Python :: 2.6\",\n          \"Programming Language :: Python :: 2.7\",\n          \"Programming Language :: Python :: 3.2\",\n          \"Programming Language :: Python :: 3.3\",\n          \"Programming Language :: Python :: 3.4\",\n          \"Programming Language :: Python :: 3.5\",\n          \"Programming Language :: Python :: 3.6\",\n          \"Programming Language :: Python :: 3.7\",\n          \"Operating System :: OS Independent\",\n          \"Topic :: Software Development :: Libraries :: Python Modules\"\n        ],\n        \"license\": \"Copyright (C) 2008-2019 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license.\"\n      },\n      \"python.project\": {\n        \"contacts\": [\n          {\n            \"email\": \"vinay_sajip@red-dove.com\",\n            \"name\": \"Vinay Sajip\",\n            \"role\": \"author\"\n          },\n          {\n            \"email\": \"vinay_sajip@red-dove.com\",\n            \"name\": \"Vinay Sajip\",\n            \"role\": \"maintainer\"\n          }\n        ],\n        \"project_urls\": {\n          \"Home\": \"http://packages.python.org/python-gnupg/index.html\"\n        }\n      }\n    },\n    \"metadata_version\": \"2.0\",\n    \"name\": \"python-gnupg\",\n    \"python.exports\": {\n      \"modules\": [\n        \"gnupg\"\n      ]\n    },\n    \"source_url\": \"https://pypi.io/packages/source/p/python-gnupg/python-gnupg-0.4.5.tar.gz\",\n    \"summary\": \"A wrapper for the Gnu Privacy Guard (GPG or GnuPG)\",\n    \"version\": \"0.4.5\"\n  },\n  \"metadata\": {\n    \"description\": \"This module allows easy access to GnuPG's key management, encryption and signature functionality from Python programs. It is intended for use with Python 2.4 or greater.\",\n    \"name\": \"python-gnupg\",\n    \"platform\": \"No particular restrictions\",\n    \"version\": \"0.4.5\"\n  },\n  \"source\": {\n    \"modules\": [\n      \"gnupg\"\n    ]\n  },\n  \"version\": 1\n}\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n    \"setuptools >= 44\",\n]\nbuild-backend = 'setuptools.build_meta'\n"
  },
  {
    "path": "release",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright (C) 2023 Red Dove Consultants Limited\n#\nimport argparse\nimport glob\nimport logging\nimport os\nimport re\nimport subprocess\nimport sys\n\nDEBUGGING = 'PY_DEBUG' in os.environ\n\nlogger = logging.getLogger(__name__)\n\n\ndef main():\n    fn = os.path.basename(__file__)\n    fn = os.path.splitext(fn)[0]\n    lfn = os.path.expanduser('~/logs/%s.log' % fn)\n    if os.path.isdir(os.path.dirname(lfn)):\n        logging.basicConfig(level=logging.DEBUG, filename=lfn, filemode='w',\n                            format='%(message)s')\n    adhf = argparse.ArgumentDefaultsHelpFormatter\n    ap = argparse.ArgumentParser(formatter_class=adhf, prog=fn)\n    aa = ap.add_argument\n    aa('-b', '--build', default=False, action='store_true', help='Force a rebuild')\n    aa('-u', '--upload', default=False, action='store_true', help='Upload to PyPI')\n    options = ap.parse_args()\n    with open('gnupg.py') as f:\n        data = f.read()\n    m = re.search(r\"__version__\\s*=\\s*'(.*)'\", data)\n    assert m\n    ver = m.groups()[0]\n    sigs = list(glob.glob(f'dist/*{ver}*.asc'))\n    # import pdb; pdb.set_trace()\n    if sigs and not options.build:\n        print(f'Signatures found: {\", \".join(sigs)}')\n    else:\n        if not sigs:\n            print('Signatures not found ...')\n        files = [fn for fn in glob.glob(f'dist/*{ver}*') if not fn.endswith('.asc')]\n        if files and not options.build:\n            print(f'Archives found: {\", \".join(files)}')\n        else:\n            if not files:\n                print('Archives not found ...')\n            subprocess.check_call(['pybuild'])\n            files = [fn for fn in glob.glob(f'dist/*{ver}*') if not fn.endswith('.asc')]\n        for fn in files:\n            sfn = f'{fn}.asc'\n            if os.path.exists(sfn):\n                os.remove(sfn)\n            cmd = ['gpg2', '-abs', fn]\n            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n            p.communicate()\n            assert p.returncode == 0\n    if options.upload:\n        cmd = ['twine', 'upload', '-r', 'python-gnupg']\n        cmd.extend(files)\n        subprocess.check_call(cmd)\n\n\nif __name__ == '__main__':\n    try:\n        rc = main()\n    except KeyboardInterrupt:\n        rc = 2\n    except Exception as e:\n        if DEBUGGING:\n            s = ' %s:' % type(e).__name__\n        else:\n            s = ''\n        sys.stderr.write('Failed:%s %s\\n' % (s, e))\n        if DEBUGGING: import traceback; traceback.print_exc()\n        rc = 1\n    sys.exit(rc)\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nname = python-gnupg\nversion = attr: gnupg.__version__\ndescription = A wrapper for the Gnu Privacy Guard (GPG or GnuPG)\nlong_description =\n  This module allows easy access to GnuPG's key management, encryption and signature\n  functionality from Python programs. It is intended for use with Python 2.7 or\n  greater.\n\n  Releases are normally signed using a GnuPG key with the user id\n  vinay_sajip@yahoo.co.uk and the following fingerprint:\n\n  CA74 9061 914E AC13 8E66  EADB 9147 B477 339A 9B86\n\n  As PyPI no longer shows signatures, you should be able to download release archives\n  and signatures from\n\n  https://github.com/vsajip/python-gnupg/releases/\n\n  The archives should be the same as those uploaded to PyPI.\n\nurl = https://github.com/vsajip/python-gnupg\nauthor = Vinay Sajip\nauthor_email = vinay_sajip@yahoo.co.uk\nmaintainer = Vinay Sajip\nmaintainer_email = vinay_sajip@yahoo.co.uk\nlicense = BSD\nlicense_files = LICENSE.txt\nplatforms = any\nrequires_python >= 2.7\nclassifiers =\n    Development Status :: 5 - Production/Stable\n    Intended Audience :: Developers\n    License :: OSI Approved :: BSD License\n    Programming Language :: Python\n    Programming Language :: Python :: 2\n    Programming Language :: Python :: 3\n    Programming Language :: Python :: 2.7\n    Programming Language :: Python :: 3.6\n    Programming Language :: Python :: 3.7\n    Programming Language :: Python :: 3.8\n    Programming Language :: Python :: 3.9\n    Programming Language :: Python :: 3.10\n    Programming Language :: Python :: 3.11\n    Programming Language :: Python :: 3.12\n    Programming Language :: Python :: 3.13\n    Programming Language :: Python :: 3.14\n    Operating System :: OS Independent\n    Topic :: Software Development :: Libraries :: Python Modules\nproject_urls =\n    Documentation = https://gnupg.readthedocs.io/\n    Source = https://github.com/vsajip/python-gnupg\n    Tracker = https://github.com/vsajip/python-gnupg/issues\nkeywords = GnuPG,cryptography,encryption,decryption,signing,verification\n\n[options]\npy_modules = gnupg\n\n[bdist_wheel]\npython_tag = py2.py3\n"
  },
  {
    "path": "test_gnupg.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nA test harness for gnupg.py.\n\nCopyright (C) 2008-2026 Vinay Sajip. All rights reserved.\n\"\"\"\nimport argparse\nimport io\nimport json\nimport logging\nimport os.path\nimport os\nimport re\nimport shutil\nimport stat\nimport sys\nimport tempfile\nimport unittest\n\ntry:\n    unicode\nexcept NameError:\n    unicode = str\n\ntry:\n    from unittest import skipIf\nexcept ImportError:  # pragma: no cover\n    # For now, for Python < 2.7\n    def skipIf(condition, message):\n        if not condition:\n            return lambda x: x\n        else:\n            return lambda x: None\n\n\nimport gnupg\n\n__author__ = 'Vinay Sajip'\n__date__ = '$31-Dec-2025 16:42:39$'\n\nALL_TESTS = True\n\ngnupg.log_everything = True\n\nlogger = logging.getLogger(__name__)\n\nGPGBINARY = os.environ.get('GPGBINARY', 'gpg')\n\nKEYS_TO_IMPORT = \"\"\"-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v1.4.9 (MingW32)\n\nmQGiBEiH4QERBACm48JJsg2XGzWfL7f/fjp3wtrY+JIz6P07s7smr35kve+wl605\nnqHtgjnIVpUVsbI9+xhIAPIkFIR6ZcQ7gRDhoT0bWKGkfdQ7YzXedVRPlQLdbpmR\nK2pKKySpF35pJsPAYa73EVaxu2KrII4CyBxVQgNWfGwEbtL5FfzuHhVOZwCg6JF7\nbgOMPmEwBLEHLmgiXbb5K48D/2xsXtWMkvgRp/ubcLxzbNjaHH6gSb2IfDi1+W/o\nBmfua6FksPnEDn7PWnBhCEO9rf1tV0FcrvkR9m2FGfx38tjssxDdLvX511gbfc/Q\nDJxZ00A63BxI3xav8RiXlqpfQGXpLJmCLdeCh5DXOsVMCfepqRbWyJF0St7LDcq9\nSmuXA/47dzb8puo9dNxA5Nj48I5g4ke3dg6nPn7aiBUQ35PfXjIktXB6/sQJtWWx\nXNFX/GVUxqMM0/aCMPdtaoDkFtz1C6b80ngEz94vXzmON7PCgDY6LqZP1B1xbrkr\n4jGSr68iq7ERT+7E/iF9xp+Ynl91KK7h8llY6zFw+yIe6vGlcLQvR2FyeSBHcm9z\ncyAoQSB0ZXN0IHVzZXIpIDxnYXJ5Lmdyb3NzQGdhbW1hLmNvbT6IYAQTEQIAIAUC\nSIfhAQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEJZ2Ekdc7S4UtEcAoJIA\niZurfuzIUE9Dtn86o6vC14qoAJ9P79mxR88wRr/ac9h5/BIf5cZKMbkCDQRIh+EB\nEAgAyYCvtS43J/OfuGHPGPZT0q8C+Y15YLItSQ3H6IMZWFY+sX+ZocaIiM4noVRG\n+mrEqzO9JNh4KP1OdFju1ZC8HZXpPVur48XlTNSm0yjmvvfmi+aGSuyQ0NkfLyi1\naBeRvB4na/oFUgl908l7vpSYWYn4EY3xpvwJdyTWHTh4o7+zvrR1fByDt49k2b3z\nyTACoxYPVQfknt8gxqLqHZsbgn02Ml7HS17bSWr5Z7PlWqDlmsdqUikVU9d2RvIq\nR+YIJbOdHSklbVQQDhr+xgHPi39e7nXMxR/rMjMbz7E5vSNkge45n8Pzim8iyqy+\nMTMW8psV/OyrHUJzBEA7M6hA1wADBwgAnB0HzI1iyiQmIymO0Hj0BgqU6/avFw9R\nggBuE2v7KsvuLP6ohXDEhYopjw5hgeotobpg6tS15ynch+6L8uWsJ0rcY2X9dsJy\nO8/5mjrNDHwCKiYRuZfmRZjzW03vO/9+rjtZ0NzoWYMP3UR8lUTVp2LTygefBA88\nZgw6dWBVzn+/c0vdwcF4Y3njYKE7eq4VrfcwqRgD0hDyIJd1OpqzHfXXnTtLlAsm\nUwtdONzlwu7KkgafMo4vzKY6dCtUkR6pXAE/rLQfCTonwl9SnyusoYZgjDoj4Pvw\nePxIl2q05dcn96NJGS+SfS/5B4H4irbfaEYmCfKps+45sjncYGhZ/ohJBBgRAgAJ\nBQJIh+EBAhsMAAoJEJZ2Ekdc7S4U2lkAoIwZLMHVldC0v9wse53xU0NsNIskAKDc\nFt0XWUJ9yajOEUqCVHNs3F99t5kBogRIh+FVEQQAhk/ROtJ5/O+YERl4tZZBEhGH\nJendDBDfzmfRO9GIDcZI20nx5KJ1M/zGguqgKiVRlBy32NS/IRqwSI158npWYLfJ\nrYCWrC2duMK2i/8prOEfaktnqZXVCHudGtP4mTqNSs+867LnGhQ4w3HmB09zCIpD\neIhhhPOb5H19H8UlojsAoLwsq5BACqUKoiz8lUufpTTFMbaDA/4v1fWmprYAxGq9\ncZ9svae772ymN/RRPDb/D+UJoJCCJSjE8m4MukVchyJVT8GmpJM2+dlt62eYwtz8\nbGNt+Yzzxr0N8rLutsSks7RaM16MaqiAlM20gAXEovxBiocgP/p5bO3FGKOBbrfd\nh47BZDEqLvfJefXjZEsElbZ9oL2zDgP9EsoDS9mbfesHDsagE5jCZRTY1C/FRLBO\nzhGgP2IlqBdOX8BYBYZiIlLM+pN5fU0Hcu3VOZY1Hnj6r3VbK1bOScQzqrZ7qgmw\nTRgyxUQalaOhMb5rUD0+dUFxa/mhTerx5POrX6zOWmmK0ldYTZO4/+nWr4FwmU8R\n41nYYYdi0yS0MURhbm55IERhdmlzIChBIHRlc3QgdXNlcikgPGRhbm55LmRhdmlz\nQGRlbHRhLmNvbT6IYAQTEQIAIAUCSIfhVQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4B\nAheAAAoJEG7bKmS7rMYAEt8An2jxsmsE1MZVZc4Ev8RB9Gu1zbsCAJ9G5kkYIIf0\nOoDqCjkDMDJcpd4MqLkCDQRIh+FVEAgAgHQ+EyseLw6A3BS2EUz6U1ZGzuJ5CXxY\nBY8xaQtE+9AJ0WHyzKeptnlnY1x9et3ny1BcVC5aR1OgsDiuVRvSFwpFfVxMKbRT\nkvERWADfB0N5EyWwyE0E4BT5hyEhW7fS0bucJL6UK5PKvfE5wexWlUI3yV4K1z6W\n2gSNL60o3kmoGn9K5ICWO/jbi6MkPptSoDu/laCJHv/aid6Gf94ckDClQQyLsccj\n0ibynm6rI3cIzpPMbimKIsKT1smAqZEBsTucBlOjIuIROANTZUN3reGIRh/kVNyg\nYTrkUnIqVS9FnbHa2wxeb6F/cO33fPiVfiCmZuKI1Uh4PMGaaSCh0wADBQf/SaXN\nWcuD0mrEnxqgEJRx67ZeFZjZM53Obu3JYQ++lqsthf8MxE7K4J/67xDpOh6waK0G\n6GCLwEm3Z7wjCaz1DYg2uJp/3pispWxZio3PLVe7WrMY+oEBHEsiJXicS5dV620a\nuoaBnnc0aQWT/DREE5s35IrZCh4WDQgO9rl0i/qcIITm77TmQbq2Xdj5vt6s0cx7\noHKRaFBpQ8DBsCQ+D8Xz7i1oUygNp4Z5xPhItWeCfE9YoCoem4jSB4HGwmMOEicp\nVSpY43k01cd0Yfb1OMhA5C8OBwcwn3zvQB7nbxyxyQ9qphfwhMookIL4+tKKBIQL\nCnOGhApkAGbjRwuLi4hJBBgRAgAJBQJIh+FVAhsMAAoJEG7bKmS7rMYA+JQAn0E2\nWdPQjKEfKnr+bW4yubwMUYKyAJ4uiE8Rv/oEED1oM3xeJqa+MJ9V1w==\n=sqld\n-----END PGP PUBLIC KEY BLOCK-----\"\"\"\n\nSIGNED_KEYS = \"\"\"-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2\n\nmI0EVcnKUQEEAKWazmfM0kbvDdw7Kos2NARaX67c8iJ3GOBimUvYLj4VR3Mqrm34\nZdLlS8jCmid+qoisefvGW5uw5Q3gIs0mdEdUpFKlXNiIja/Dg/FHjjJPPCjfzDTh\nQ03EYA7QvOnXZXhYPBqK7NitsNXW4lPnIJdanLx7yMuL+2Xb+tF39mwnABEBAAG0\nLUpvc2h1YSBDYWx2ZXJ0IChBIHRlc3QgdXNlcikgPGpjQGV4YW1wbGUuY29tPoi3\nBBMBCAAhBQJVycpRAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJELxvNQ+z\n0EB2jcED/0lHKaEkyd6cj0Zckf9luIkZ4Hno/vRCquTI7c3aPjS3qmE8mOvKSBCV\n+SamPdRM7DdjkdBrrKy2HtiDqbM+1/CdXuQka2SlJWyLCJe48+KWfBpqlY3N4t53\nJjHRitDB+hC8njWTV5prli6EgsBPAF+ZkO0iZhlsMmWdDWgqDpGRiJwEEAEIAAYF\nAlXJym8ACgkQBXzPZYwHT9oiiQQAvPF8ubwRopnXIMDQgSxKyFDM1MI1w/wb4Okd\n/MkMeZSmdcHJ6pEymp5bYciCBuLW+jw0vZWza3YloO/HtuppnF6A9a1UvYcp/diI\nO5qkQqYPlui1PJl7hQ014ioniMfOcC4X/r6PDbC78Pczje0Yh9AOqNGeCyNyNdlc\npjaHb0m4jQRVycpRAQQAo9JjW75F5wTVVO552cGCZWqZvDyBt9+IkoK9Bc+ggdn5\n6R8QVCihYuaSzcSEN84zHaR3MmGKHraCmCSlfe7w0d41Dlns0P03KMdIZOGrm045\nF8TXdSSPQOv5tA4bz3k2lGD0zB8l4NUWFaZ5fzw2i73FF4O/FwCU8xd/JCKVPkkA\nEQEAAYifBBgBCAAJBQJVycpRAhsMAAoJELxvNQ+z0EB2xLYD/i3tKirQlVB+32WP\nwggstqDp1BlUBmDb+4Gndpg4l7omJTTyOsF26SbYgXZqAdEd5T/UfpEla0DKiBYh\n2/CFYXadkgX/ME+GTetTmD4hHoBNmdXau92buXsIXkwh+JR+RC3cl2U6tWb/MIRd\nzvJiok8W8/FT/QrEjIa2etN2d+KR\n=nNBX\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2\n\nmI0EVcnKNgEEANIVlIUyRXWHP/ljdMEA8B5NxecRCKusUIPxeapk2do5UCZgR1q8\n5wOP4K/+W3Uj85ylOOCNTFYKRozAHsPMAmQ38W93DZYqFbG6d7rwMvz4pVe0wUtj\nSBINoKnoEDZwx3erxFKOkp/5fF3NoYSIx9a0Ds21ESk0TAuH5Tg934YhABEBAAG0\nMVdpbnN0b24gU21pdGggKEEgdGVzdCB1c2VyKSA8d2luc3RvbkBleGFtcGxlLmNv\nbT6ItwQTAQgAIQUCVcnKNgIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRAF\nfM9ljAdP2h05A/4vmnxV1MwcOhJTHZys5g2/j5UoZG7V7lPGpJaojSAIVzYXZtwT\n5A7OY8Nl21kIY6gnZlgbTRpHN8Qq2wRKAyW5o6wQvuN16CW4bmGjoHYRGPqkeM0w\nG40W/v88JXrYDNNe/68g4pnPsZ3J0oMLbRvCaDQQHXBuZNJrT1sOxl9Of7iNBFXJ\nyjYBBACmHbs0PdOF8NEGc+fEtmdKOSKOkrcvg1wTu1KFFTBFEbseHOCNpx+R6lfO\nZiZmHGdKeJhTherfjHaY5jmvyDWq5TLZXK61quNsWxmY2zJ0SRwrIG/CWi4bMi5t\nJNc23vMumkz4X5g7x0Ea7xEWkcYBn0H6sZDAtb8d8mrlWkMekQARAQABiJ8EGAEI\nAAkFAlXJyjYCGwwACgkQBXzPZYwHT9pQIwP8D9/VroykSE2J3gy0S6HC287jXqXF\n0zWejUAQtWUSSRx4esqfLE8lfae6+LDHO8D0Bf6YUJmu7ATOZP2/TIas7JrNvXWc\nNKWl2MHEAGUYq8utCjZ3dKKhaV7UvcY4PyLIpFteNkOz4wFe6C0Mm+1NYwokIFyh\nzPBq9eFk7Xx9Wrc=\n=HT6N\n-----END PGP PUBLIC KEY BLOCK-----\n\"\"\"\n\nSECRET_KEY = \"\"\"\n-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlQPGBFztd1UBCACiHhlEJIGfXNEiUX4GwamgdLOkJ3mbn5OyV4M/Ie3YvvHxveq/\nTFYbuV63iuDVhNXpDUNmGsTq4vFaMsseLl7eESw8UTa3XklHHjh56kw0AVkJA75A\nXq/VshFobLNxYZdtlOVkKe1a3uJVKs+BqFjhavEjQyhkpWvBY51OzCSc2AN/aQZA\nF3AltZ8luIHZPs8zVbgH90WIpze+vzAd9FyXD0wV6gylGSifHj8zIhac80evQgD9\n50De7EPnSdgZSNwnlrhQtAIB5UnTETxXk34/W0Rq+BKn6SuchtaP7hXIHC0+B0C7\nzBzPYKMQ7vXc/hceNwSGtgovhaQPCcv1byFBABEBAAH+BwMCUNdAVY/RMdJg1q5n\nFQOyVZl2tvd3krExjGYvhabwijbPz+TrVkPhKqdkp4Hbf3oXV/bcbQhG2dld4Ooc\n+xtEpTqYw08bNDuk4NEAvggasUkgssHZccDmHySGfA9U8C7B0Hj8xT4SifnuVNL+\nxp9iv1BS03s+UIEVZ2rGjDQy7/G/U6/ZpLqFg+C113VQs6yz0VMsnnAQOMgN0+gQ\naZb6VNPR7nZ5+/hRlx0DgXu++lei9HTmHRz+ZvbbYjeU9nj10eANhO0lEvlgtyXa\nv4Y5ERwk86gbkSRGtN88qVK/+GXK60Q33EoGMlwPZrfFGx+N5QuPEnCjT1vvz7E3\nHhCpe4u5Idusgui+tDkxq8BEz6iTGMO1hcb75MDdIQBhJzeJ7OIxyBfqLReF4+Ut\neNwy0wpN3xuEeYvP4ZIe7hj74WWIuKq2+lesPm4eWRPoaQ5MZXmEwbjr29e++V7D\nEkHgCYio6TVwrHA0LRSNfm8VVBV2cdsqFOLLutudHoC8BnjetEetmYaA99u0Pevz\nNscYwfaWLNW/d5FGyPUb+GQFYzmQWUfUzpg9hu7U79uA0kOwC+4nK6LEalILtoHn\nYO3PvvcCEnpWBlDhCR3n0zkNQCulvQKS/ww5q/MDNqvibKiMJHJ1xP89tEU3lnHl\nqgwHVmleqUR+yzdg5lo96Yey5yaDdhK5ZR1TFC4qK4Igcn2+WG109659bJUGpEre\nVktu530JutX38ZoyKdHO0uPs/ft/hgBhNd6MKmh7eejo84Wn6/lxkfMydkfKm5QY\ndMHF3Ew+l7aACAs3l95V0YDNzA0FyOFkb/tqxyx8dP+O2NdZQZSvG+yxDav05bCq\nkwz+7H7sJnUj1JJtUgPTL9yVH+LyUhL8AU13UKVjBFJ4VL5+KDD9KwPkk6aN7zDW\nQv0g8Cc7A8H0tB5BdXRvZ2VuZXJhdGVkIEtleSA8dXNlcjFAdGVzdD6JATgEEwEI\nACIFAlztd1UCGy8GCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEGP32fXSIJgg\nIXgH/3o1rUzbjjz1sMoBwRv4qLmgeqlB2YJSVzLWOn4AcrHbxup5O9nJkqG+YFwH\nOFmytuiPDKmA4ZXww8f+2rHXdDuwI5SWnfhuPpV863BulIhtjwiwqD9eIzQ9LX79\nK7hXRJ4I0AkYEbDHOWlLHZCrjul/ZaS10QRVR21EYICha2I8tvxsRMPp0I93XnuB\nT+z7ykRxRjpMv6MfhWVcw5B0s7lPedLhcx657HfY49t36/CIZ9/zMKsduX7cTOAh\ntO8f06R3yfjxLRD8y89frVP3+tGMvt2yGOd5TT0zht5yYcG6QkiHlfdgXqeE8nsU\n2392Xn/RETq6xCj3kG6K3wbWqh0=\n=2A5s\n-----END PGP PRIVATE KEY BLOCK-----\n\"\"\"\n\nCERTIFYING_KEY = \"\"\"\n-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlHcEaeE/WBMIKoZIzj0DAQcCAwQJX+QJbszp7FFHIaGY1ZOwLJCTnwjzy1Z5vnKw\n1AZ9UnIRO+TMPEEUizEc4FO1nQBUgCS2nOccwXpnZtavc8d5AAD/TitHUDwl1CbF\nf2FGF4alBhMBuWohWcAUNOopbKgaNO4MubQfQ2VydGlmaWVyIFRlc3QgPGNlcnRp\nZmllckB0ZXN0PoiQBBMTCAA4FiEEl+gdlmY3v7p/43AlhW+ORWgM16QFAmnhP1gC\nGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQhW+ORWgM16RwdQEA9CTM/Zz+\nrWNl3ToKdsPKS7s3KaPfvGPKNIqVwUJzhT8BAN58ziizYcb85HREsFHtYOJs0Uti\n7GYLD4MPZxhIz5sr\n=he2w\n-----END PGP PRIVATE KEY BLOCK-----\n\"\"\"\n\nRECIPIENT_KEY = \"\"\"\n-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlHcEaeE//hMIKoZIzj0DAQcCAwRraYDaESix05+l8b69fKzIvYmIoXbaOVPoCnjA\nQe6hYEKQrO7p5zOUp6lLXhnZ6JWD6B7RcoGSHpAHQMWzpzkWAAEA92FWKM7TZolx\nWpvuj+6lLf6wrg/gOVofvjKDoj9IbfARALQfUmVjaXBpZW50IFRlc3QgPFJlY2lw\naWVudEB0ZXN0PoiQBBMTCAA4FiEEqHrbKqME4ufCGsGh2ILlpHJXa3EFAmnhP/4C\nGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ2ILlpHJXa3E2NAEAlbZ+sUpL\nj88bPK7sBA0pgiAItWYclDgZXAmqBCXz9ggBAM4khSii4pshJh0XavURaYnoC2SU\nqlWoXWGVJkVDlHla\n=1zeo\n-----END PGP PRIVATE KEY BLOCK-----\n\"\"\"\n\n\ndef is_list_with_len(o, n):\n    return isinstance(o, list) and len(o) == n\n\n\nBASE64_PATTERN = re.compile(r'^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$', re.I)\n\n\ndef get_key_data(s):\n    lines = s.split('\\n')\n    result = ''\n    for line in lines:\n        m = BASE64_PATTERN.match(line)\n        if m:\n            result += line\n    return result\n\n\ndef compare_keys(k1, k2):\n    \"Compare ASCII keys\"\n    # See issue #57: we need to compare only the actual key data,\n    # ignoring things like spurious blank lines\n    return get_key_data(k1) != get_key_data(k2)\n\n\nAGENT_CONFIG = '''allow-loopback-pinentry\nlog-file socket:///tmp/S.my-gnupg-log\nverbose\ndebug ipc\n'''\n\nENABLE_TOFU = 'ENABLE_TOFU' in os.environ\n\nif ENABLE_TOFU:  # pragma: no cover\n    GPG_CONFIG = 'trust-model tofu+pgp\\ntofu-default-policy unknown\\n'\n\n\ndef prepare_homedir(hd):\n    if not os.path.isdir(hd):  # pragma: no cover\n        os.makedirs(hd)\n    os.chmod(hd, 0x1C0)\n    fn = os.path.join(hd, 'gpg-agent.conf')\n    with open(fn, 'w') as f:\n        f.write(AGENT_CONFIG)\n    if ENABLE_TOFU:  # pragma: no cover\n        fn = os.path.join(hd, 'gpg.conf')\n        with open(fn, 'w') as f:\n            f.write(GPG_CONFIG)\n\n\nclass GPGTestCase(unittest.TestCase):\n\n    def setUp(self):\n        ident = self.id().rsplit('.', 1)[-1]\n        logger.debug('-- %s starting ---------------------------' % ident)\n        if 'STATIC_TEST_HOMEDIR' not in os.environ:\n            hd = tempfile.mkdtemp(prefix='keys-')\n        else:  # pragma: no cover\n            hd = os.path.join(os.getcwd(), 'keys')\n            if os.path.exists(hd):\n                self.assertTrue(os.path.isdir(hd), 'Not a directory: %s' % hd)\n                shutil.rmtree(hd, ignore_errors=True)\n        prepare_homedir(hd)\n        self.homedir = hd\n        self.gpg = gpg = gnupg.GPG(gnupghome=hd, gpgbinary=GPGBINARY)\n        v = gpg.version\n        if v:\n            if v >= (2, ):  # pragma: no cover\n                gpg.options = ['--debug-quick-random']\n            else:\n                gpg.options = ['--quick-random']\n        self.test_fn = test_fn = 'random_binary_data'\n        if not os.path.exists(test_fn):  # pragma: no cover\n            data_file = open(test_fn, 'wb')\n            data_file.write(os.urandom(5120 * 1024))\n            data_file.close()\n\n    def tearDown(self):\n        if 'STATIC_TEST_HOMEDIR' not in os.environ:\n            shutil.rmtree(self.homedir, ignore_errors=True)\n        ident = self.id().rsplit('.', 1)[-1]\n        logger.debug('-- %s finished ---------------------------' % ident)\n\n    def test_environment(self):\n        \"Test the environment by ensuring that setup worked\"\n        hd = self.homedir\n        self.assertTrue(os.path.exists(hd) and os.path.isdir(hd), 'Not an existing directory: %s' % hd)\n\n    def test_list_keys_initial(self):\n        \"Test that initially there are no keys\"\n        public_keys = self.gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 0), 'Empty list expected')\n        private_keys = self.gpg.list_keys(secret=True)\n        self.assertTrue(is_list_with_len(private_keys, 0), 'Empty list expected')\n\n    def generate_key(self, first_name, last_name, domain, passphrase=None, with_subkey=True):\n        \"Generate a key\"\n        params = {\n            'Key-Type': 'DSA',\n            'Key-Length': 1024,\n            'Name-Comment': 'A test user',\n            'Expire-Date': 0,\n        }\n        if with_subkey:\n            params['Subkey-Type'] = 'ELG-E'\n            params['Subkey-Length'] = 2048\n\n        options = self.gpg.options or []\n        if '--debug-quick-random' in options or '--quick-random' in options:\n            # If using the fake RNG, a key isn't regarded as valid\n            # unless its comment has the text (insecure!) in it.\n            params['Name-Comment'] = 'A test user (insecure!)'\n        params['Name-Real'] = '%s %s' % (first_name, last_name)\n        params['Name-Email'] = ('%s.%s@%s' % (first_name, last_name, domain)).lower()\n        if passphrase is None:\n            passphrase = ('%s%s' % (first_name[0], last_name)).lower()\n        params['Passphrase'] = passphrase\n        cmd = self.gpg.gen_key_input(**params)\n        return self.gpg.gen_key(cmd)\n\n    def do_key_generation(self):\n        \"Test that key generation succeeds\"\n        result = self.generate_key('Barbara', 'Brown', 'beta.com')\n        self.assertNotEqual(None, result, 'Non-null result')\n        return result\n\n    def test_key_generation_with_invalid_key_type(self):\n        \"Test that key generation handles invalid key type\"\n        params = {\n            'Key-Type': 'INVALID',\n            'Key-Length': 1024,\n            'Subkey-Type': 'ELG-E',\n            'Subkey-Length': 2048,\n            'Name-Comment': 'A test user',\n            'Expire-Date': 0,\n            'Name-Real': 'Test Name',\n            'Name-Email': 'test.name@example.com',\n        }\n        cmd = self.gpg.gen_key_input(**params)\n        result = self.gpg.gen_key(cmd)\n        self.assertFalse(result.data, 'Null data result')\n        self.assertFalse(result.fingerprint, 'Null fingerprint result')\n        self.assertEqual(2, result.returncode, 'Unexpected return code')\n\n    def test_key_generation_with_colons(self):\n        \"Test that key generation handles colons in key fields\"\n        params = {\n            'key_type': 'RSA',\n            'name_real': 'urn:uuid:731c22c4-830f-422f-80dc-14a9fdae8c19',\n            'name_comment': 'dummy comment',\n            'name_email': 'test.name@example.com',\n        }\n        if self.gpg.version >= (2, 1):\n            params['passphrase'] = 'foo'\n        cmd = self.gpg.gen_key_input(**params)\n        result = self.gpg.gen_key(cmd)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        keys = self.gpg.list_keys()\n        self.assertEqual(0, keys.returncode, 'Non-zero return code')\n        self.assertEqual(len(keys), 1)\n        key = keys[0]\n        uids = key['uids']\n        self.assertEqual(len(uids), 1)\n        uid = uids[0]\n        self.assertEqual(uid, 'urn:uuid:731c22c4-830f-422f-80dc-14a9fdae8c19 '\n                         '(dummy comment) <test.name@example.com>')\n\n    def test_key_generation_with_escapes(self):\n        \"Test that key generation handles escape characters\"\n        params = {\n            'name_real': 'Test Name',\n            'name_comment': 'Funny chars: \\\\r\\\\n\\\\f\\\\v\\\\0\\\\b',\n            'name_email': 'test.name@example.com',\n        }\n        if self.gpg.version >= (2, 1):\n            params['passphrase'] = 'foo'\n        cmd = self.gpg.gen_key_input(**params)\n        result = self.gpg.gen_key(cmd)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        keys = self.gpg.list_keys()\n        self.assertEqual(0, keys.returncode, 'Non-zero return code')\n        self.assertEqual(len(keys), 1)\n        key = keys[0]\n        uids = key['uids']\n        self.assertEqual(len(uids), 1)\n        uid = uids[0]\n        self.assertEqual(uid, 'Test Name (Funny chars: '\n                         '\\r\\n\\x0c\\x0b\\x00\\x08) <test.name@example.com>')\n\n    @skipIf(os.name == 'nt', 'Test requires POSIX-style permissions')\n    def test_key_generation_failure(self):\n        if self.gpg.version < (2, 0):  # pragma: no cover\n            raise unittest.SkipTest('gpg 1.x hangs in this test')\n        if not os.path.exists('rokeys'):  # pragma: no cover\n            os.mkdir('rokeys')\n        os.chmod('rokeys', 0o400)  # no one can write/search this directory\n        gpg = gnupg.GPG(gnupghome='rokeys', gpgbinary=GPGBINARY)\n        params = {\n            'Key-Type': 'RSA',\n            'Key-Length': 1024,\n            'Subkey-Type': 'ELG-E',\n            'Subkey-Length': 2048,\n            'Name-Comment': 'A test user',\n            'Expire-Date': 0,\n            'Name-Real': 'Test Name',\n            'Name-Email': 'test.name@example.com',\n        }\n        cmd = gpg.gen_key_input(**params)\n        result = gpg.gen_key(cmd)\n        self.assertNotEqual(result.returncode, 0)\n        self.assertEqual(result.status, 'key not created')\n\n    def test_key_generation_input(self):\n        \"Test that key generation input handles empty values, curves etc.\"\n        params = {\n            'key_type': ' ',\n            'key_length': 2048,\n        }\n        cmd = self.gpg.gen_key_input(**params)\n        self.assertTrue('Key-Type: RSA\\n' in cmd)\n        params['key_type'] = 'DSA'\n        cmd = self.gpg.gen_key_input(**params)\n        self.assertTrue('Key-Type: DSA\\n' in cmd)\n        params = {\n            'key_type': 'ECDSA',\n            'key_curve': 'nistp384',\n            'subkey_type': 'ECDH',\n            'subkey_curve': 'nistp384',\n            'name_comment': 'NIST P-384',\n        }\n        cmd = self.gpg.gen_key_input(**params)\n        for s in ('Key-Type: ECDSA', 'Key-Curve: nistp384', 'Subkey-Type: ECDH', 'Subkey-Curve: nistp384',\n                  'Name-Comment: NIST P-384'):\n            self.assertTrue('%s\\n' % s in cmd)\n        self.assertFalse('Key-Length: ' in cmd)\n\n    def test_add_subkey(self):\n        \"Test that subkeys can be added\"\n        if self.gpg.version[0] < 2:  # pragma: no cover\n            raise unittest.SkipTest('Feature unavailable in GnuPG 1.x')\n        master_key = self.generate_key('Charlie', 'Clark', 'gamma.com', passphrase='123', with_subkey=False)\n        self.assertEqual(0, master_key.returncode, 'Non-zero return code')\n\n        result = self.gpg.add_subkey(master_key=master_key.fingerprint,\n                                     master_passphrase='123',\n                                     algorithm='dsa',\n                                     usage='sign',\n                                     expire=0)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        pubkeys = self.gpg.list_keys()\n        for key in pubkeys:\n            sklist = key['subkeys']\n            skmap = key['subkey_info']\n            self.assertEqual(len(sklist), 1)\n            self.assertTrue(len(skmap), 1)\n            for sk in sklist:\n                skid, capability, fp, grp = sk\n                self.assertEqual(skmap[skid]['fingerprint'], fp)\n                self.assertEqual(skmap[skid]['keygrip'], grp)\n\n    def test_add_subkey_with_invalid_key_type(self):\n        \"Test that subkey generation handles invalid key type\"\n        if self.gpg.version[0] < 2:  # pragma: no cover\n            raise unittest.SkipTest('Feature unavailable in GnuPG 1.x')\n        master_key = self.generate_key('Charlie', 'Clark', 'gamma.com', passphrase='123', with_subkey=False)\n        self.assertEqual(0, master_key.returncode, 'Non-zero return code')\n\n        result = self.gpg.add_subkey(master_key=master_key.fingerprint,\n                                     master_passphrase='123',\n                                     algorithm='INVALID',\n                                     usage='sign',\n                                     expire=0)\n\n        self.assertFalse(result.data, 'Null data result')\n        self.assertEqual('', result.fingerprint, 'Empty fingerprint result')\n        self.assertEqual(2, result.returncode, 'Unexpected return code')\n\n    def test_deletion_subkey(self):\n        \"Test that subkey deletion works\"\n        if self.gpg.version[0] < 2:  # pragma: no cover\n            raise unittest.SkipTest('Feature unavailable in GnuPG 1.x')\n        master_key = self.generate_key('Charlie', 'Clark', 'gamma.com', passphrase='123', with_subkey=False)\n        self.assertEqual(0, master_key.returncode, 'Non-zero return code')\n\n        subkey = self.gpg.add_subkey(master_key=master_key.fingerprint,\n                                     master_passphrase='123',\n                                     algorithm='dsa',\n                                     usage='sign',\n                                     expire=0)\n        self.assertEqual(0, subkey.returncode, 'Non-zero return code')\n\n        public_keys = self.gpg.list_keys()\n        key_info = public_keys[0]\n\n        private_keys = self.gpg.list_keys(secret=True)\n        secret_key_info = private_keys[0]\n\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 1), '1-element list expected')\n        self.assertEqual(len(key_info['subkeys']), 1, '1-element list expected')\n\n        self.assertTrue(is_list_with_len(private_keys, 1), '1-element list expected')\n        self.assertEqual(len(secret_key_info['subkeys']), 1, '1-element list expected')\n        result = self.gpg.delete_keys(subkey.fingerprint, secret=True, passphrase='123', exclamation_mode=True)\n        result = self.gpg.delete_keys(subkey.fingerprint, exclamation_mode=True)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n\n        public_keys = self.gpg.list_keys()\n        key_info = public_keys[0]\n\n        private_keys = self.gpg.list_keys(secret=True)\n        secret_key_info = private_keys[0]\n\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 1), '1-element list expected')\n        self.assertEqual(len(key_info['subkeys']), 0, '0-element list expected')\n\n        self.assertTrue(is_list_with_len(private_keys, 1), '1-element list expected')\n        self.assertEqual(len(secret_key_info['subkeys']), 0, '1-element list expected')\n\n    def test_list_subkey_after_generation(self):\n        \"Test that after subkey generation, the generated subkey is available\"\n        if self.gpg.version[0] < 2:  # pragma: no cover\n            raise unittest.SkipTest('Feature unavailable in GnuPG 1.x')\n        self.test_list_keys_initial()\n\n        master_key = self.generate_key('Charlie', 'Clark', 'gamma.com', passphrase='123', with_subkey=False)\n        self.assertEqual(0, master_key.returncode, 'Non-zero return code')\n\n        subkey_sign = self.gpg.add_subkey(master_key=master_key.fingerprint,\n                                          master_passphrase='123',\n                                          algorithm='dsa',\n                                          usage='sign',\n                                          expire=0)\n        self.assertEqual(0, subkey_sign.returncode, 'Non-zero return code')\n\n        subkey_encrypt = self.gpg.add_subkey(master_key=master_key.fingerprint,\n                                             master_passphrase='123',\n                                             algorithm='rsa',\n                                             usage='encrypt',\n                                             expire=0)\n        self.assertEqual(0, subkey_encrypt.returncode, 'Non-zero return code')\n\n        public_keys = self.gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 1), '1-element list expected')\n        key_info = public_keys[0]\n        if self.gpg.version >= (2, 1):\n            self.assertTrue(key_info['keygrip'])\n        fp = key_info['fingerprint']\n        self.assertTrue(fp in public_keys.key_map)\n        self.assertTrue(public_keys.key_map[fp] is key_info)\n        self.assertEqual(fp, master_key.fingerprint)\n        self.assertTrue('subkey_info' in key_info)\n        skinfo = key_info['subkey_info']\n        self.assertEqual(len(skinfo), 2)\n        self.assertEqual(key_info['subkeys'][0][1], 's')\n        self.assertEqual(key_info['subkeys'][0][2], subkey_sign.fingerprint)\n\n        self.assertEqual(key_info['subkeys'][1][1], 'e')\n        self.assertEqual(key_info['subkeys'][1][2], subkey_encrypt.fingerprint)\n        for skid, _, sfp, grp in key_info['subkeys']:\n            self.assertTrue(skid in skinfo)\n            info = skinfo[skid]\n            self.assertEqual(info['keyid'], skid)\n            self.assertEqual(info['type'], 'sub')\n            self.assertTrue(sfp in public_keys.key_map)\n            self.assertTrue(public_keys.key_map[sfp] is key_info)\n            if self.gpg.version >= (2, 1):\n                self.assertTrue(grp)\n\n    def test_list_keys_after_generation(self):\n        \"Test that after key generation, the generated key is available\"\n        self.test_list_keys_initial()\n        self.do_key_generation()\n        public_keys = self.gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 1), '1-element list expected')\n        key_info = public_keys[0]\n        if self.gpg.version >= (2, 1):\n            self.assertTrue(key_info['keygrip'])\n        fp = key_info['fingerprint']\n        self.assertTrue(fp in public_keys.key_map)\n        self.assertTrue(public_keys.key_map[fp] is key_info)\n        self.assertTrue('subkey_info' in key_info)\n        skinfo = key_info['subkey_info']\n        for skid, _, sfp, grp in key_info['subkeys']:\n            self.assertTrue(skid in skinfo)\n            info = skinfo[skid]\n            self.assertEqual(info['keyid'], skid)\n            self.assertEqual(info['type'], 'sub')\n            self.assertTrue(sfp in public_keys.key_map)\n            self.assertTrue(public_keys.key_map[sfp] is key_info)\n            if self.gpg.version >= (2, 1):\n                self.assertTrue(grp)\n\n        # now test with sigs=True\n        public_keys_sigs = self.gpg.list_keys(sigs=True)\n        self.assertEqual(0, public_keys_sigs.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys_sigs, 1), '1-element list expected')\n        key_info = public_keys_sigs[0]\n        if self.gpg.version >= (2, 1):\n            self.assertTrue(key_info['keygrip'])\n        fp = key_info['fingerprint']\n        self.assertTrue(fp in public_keys_sigs.key_map)\n        self.assertTrue(public_keys_sigs.key_map[fp] is key_info)\n        self.assertTrue(is_list_with_len(key_info['sigs'], 2))\n        self.assertTrue('subkey_info' in key_info)\n        skinfo = key_info['subkey_info']\n        for siginfo in key_info['sigs']:\n            self.assertTrue(len(siginfo), 3)\n        for skid, _, sfp, grp in key_info['subkeys']:\n            self.assertTrue(skid in skinfo)\n            info = skinfo[skid]\n            self.assertEqual(info['keyid'], skid)\n            self.assertEqual(info['type'], 'sub')\n            self.assertTrue(sfp in public_keys_sigs.key_map)\n            self.assertTrue(public_keys_sigs.key_map[sfp] is key_info)\n            if self.gpg.version >= (2, 1):\n                self.assertTrue(grp)\n\n        private_keys = self.gpg.list_keys(secret=True)\n        self.assertEqual(0, private_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(private_keys, 1), '1-element list expected')\n        self.assertEqual(len(private_keys.fingerprints), 1)\n        key_info = private_keys[0]\n        if self.gpg.version >= (2, 1):\n            self.assertTrue(key_info['keygrip'])\n        self.assertTrue('subkey_info' in key_info)\n        skinfo = key_info['subkey_info']\n        self.assertTrue(skid in skinfo)\n        info = skinfo[skid]\n        self.assertEqual(info['keyid'], skid)\n        self.assertEqual(info['type'], 'ssb')\n\n        # Now do the same test, but using keyring and secret_keyring arguments\n        if self.gpg.version < (2, 1):  # pragma: no cover\n            pkn = 'pubring.gpg'\n            skn = 'secring.gpg'\n        else:\n            # On GnuPG >= 2.1, --secret-keyring is obsolete and ignored,\n            # and the keyring file name has changed.\n            pkn = 'pubring.kbx'\n            skn = None\n        hd = self.homedir\n        if os.name == 'posix':\n            pkn = os.path.join(hd, pkn)\n            if skn:  # pragma: no cover\n                skn = os.path.join(hd, skn)\n        gpg = gnupg.GPG(gnupghome=hd, gpgbinary=GPGBINARY, keyring=pkn, secret_keyring=skn)\n        logger.debug('Using keyring and secret_keyring arguments')\n        public_keys_2 = gpg.list_keys()\n        self.assertEqual(0, public_keys_2.returncode, 'Non-zero return code')\n        self.assertEqual(public_keys_2, public_keys)\n        private_keys_2 = gpg.list_keys(secret=True)\n        self.assertEqual(0, private_keys_2.returncode, 'Non-zero return code')\n        self.assertEqual(private_keys_2, private_keys)\n\n        # generate additional keys so that we can test listing a subset of\n        # keys\n        def get_names(key_map):\n            result = set()\n            for info in key_map.values():\n                for uid in info['uids']:\n                    uid = uid.replace(' (A test user (insecure!))', '')\n                    result.add(uid)\n            return result\n\n        result = self.generate_key('Charlie', 'Clark', 'gamma.com')\n        self.assertNotEqual(None, result, 'Non-null result')\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        result = self.generate_key('Donna', 'Davis', 'delta.com')\n        self.assertNotEqual(None, result, 'Non-null result')\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        public_keys = gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertEqual(len(public_keys), 3)\n        actual = get_names(public_keys.key_map)\n        expected = set([\n            'Barbara Brown <barbara.brown@beta.com>', 'Charlie Clark <charlie.clark@gamma.com>',\n            'Donna Davis <donna.davis@delta.com>'\n        ])\n        self.assertEqual(actual, expected)\n        # specify a single key as a string\n        public_keys = gpg.list_keys(keys='Donna Davis')\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        actual = get_names(public_keys.key_map)\n        expected = set(['Donna Davis <donna.davis@delta.com>'])\n        self.assertEqual(actual, expected)\n        # specify multiple keys\n        public_keys = gpg.list_keys(keys=['Donna', 'Barbara'])\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        actual = get_names(public_keys.key_map)\n        expected = set(['Barbara Brown <barbara.brown@beta.com>', 'Donna Davis <donna.davis@delta.com>'])\n        self.assertEqual(actual, expected)\n\n    def test_key_trust(self):\n        \"Test that trusting keys works\"\n        gpg = self.gpg\n        result = gpg.import_keys(KEYS_TO_IMPORT)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        keys = gpg.list_keys()\n        self.assertEqual(0, keys.returncode, 'Non-zero return code')\n        fingerprints = []\n        for key in keys:\n            self.assertEqual(key['ownertrust'], '-')\n            fingerprints.append(key['fingerprint'])\n        cases = (\n            ('TRUST_NEVER', 'n'),\n            ('TRUST_MARGINAL', 'm'),\n            ('TRUST_FULLY', 'f'),\n            ('TRUST_ULTIMATE', 'u'),\n            ('TRUST_UNDEFINED', 'q'),\n            ('TRUST_EXPIRED', 'e'),\n        )\n        for param, expected in cases:\n            gpg.trust_keys(fingerprints, param)\n            keys = gpg.list_keys(keys=fingerprints)\n            for key in keys:\n                self.assertEqual(key['ownertrust'], expected)\n        self.assertRaises(ValueError, gpg.trust_keys, fingerprints, 'TRUST_FOOBAR')\n        self.assertRaises(ValueError, gpg.trust_keys, 'NO_SUCH_FINGERPRINT', 'TRUST_NEVER')\n        # gpg should raise an error for the following - but it doesn't!\n        # self.assertRaises(ValueError, gpg.trust_keys,\n        #                   'BADF00DBADF00DBADF00DBADF00DBADF00DBADF0',\n        #                   'TRUST_NEVER')\n\n    def test_list_signatures(self):\n        imported = self.gpg.import_keys(SIGNED_KEYS)\n        self.assertEqual(0, imported.returncode, 'Non-zero return code')\n        keys = self.gpg.list_keys(keys=['18897CA2'])\n        self.assertEqual(0, keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(keys, 1), 'importing test signed key')\n        sigs = self.gpg.list_keys(keys=['18897CA2'], sigs=True)[0]['sigs']\n        logger.debug('testing self-signature')\n        self.assertTrue(('BC6F350FB3D04076', 'Joshua Calvert (A test user) <jc@example.com>', '13x') in sigs)\n        logger.debug('testing subkey self-signature')\n        self.assertTrue(('BC6F350FB3D04076', 'Joshua Calvert (A test user) <jc@example.com>', '18x') in sigs)\n        logger.debug('testing other signature')\n        self.assertTrue(('057CCF658C074FDA', 'Winston Smith (A test user) <winston@example.com>', '10x') in sigs)\n\n    def test_scan_keys(self):\n        \"Test that external key files can be scanned\"\n        # Don't use SkipTest for now, as not available for Python < 2.7\n        if self.gpg.version < (2, 1):  # pragma: no cover\n            expected = set([\n                'Andrew Able (A test user) <andrew.able@alpha.com>',\n                'Barbara Brown (A test user) <barbara.brown@beta.com>',\n                'Charlie Clark (A test user) <charlie.clark@gamma.com>',\n            ])\n            test_files = ('test_pubring.gpg', 'test_secring.gpg')\n            key_fn = None\n        else:\n            expected = set([\n                'Gary Gross (A test user) <gary.gross@gamma.com>',\n                'Danny Davis (A test user) <danny.davis@delta.com>',\n            ])\n            fd, key_fn = tempfile.mkstemp(prefix='pygpg-test-')\n            os.write(fd, KEYS_TO_IMPORT.encode('ascii'))\n            os.close(fd)\n            test_files = (key_fn, )\n        try:\n            for fn in test_files:\n                logger.debug('scanning keys in %s', fn)\n                data = self.gpg.scan_keys(fn)\n                self.assertEqual(0, data.returncode, 'Non-zero return code')\n                uids = set()\n                for d in data:\n                    uids.add(d['uids'][0])\n                self.assertEqual(uids, expected)\n        finally:\n            if key_fn:\n                os.remove(key_fn)\n\n    def test_scan_keys_mem(self):\n        \"Test that external keys in memory can be scanned\"\n        expected = set([\n            'Gary Gross (A test user) <gary.gross@gamma.com>',\n            'Danny Davis (A test user) <danny.davis@delta.com>',\n        ])\n        for key in (KEYS_TO_IMPORT, ):\n            logger.debug('testing scan_keys')\n            data = self.gpg.scan_keys_mem(key)\n            self.assertEqual(0, data.returncode, 'Non-zero return code')\n            uids = set()\n            for d in data:\n                uids.add(d['uids'][0])\n            self.assertEqual(uids, expected)\n\n    def test_quick_sign_key(self):\n        \"Test the quick-sign-key functionality\"\n        if self.gpg.version < (2, 0):  # pragma: no cover\n            raise unittest.SkipTest('No support for feature in gpg 1.x')\n        # GPG requires real random when signing keys\n        self.gpg.options.remove('--debug-quick-random')\n\n        recipient_key = self.gpg.import_keys(RECIPIENT_KEY)\n        certifying_key = self.gpg.import_keys(CERTIFYING_KEY)\n        self.assertEqual(len(set(recipient_key.fingerprints)), 1)\n        self.assertEqual(len(set(certifying_key.fingerprints)), 1)\n        certifying_fingerprint = certifying_key.fingerprints[0]\n        recipient_fingerprint = recipient_key.fingerprints[0]\n\n        sign_result = self.gpg.quick_sign_key(certifying_fingerprint, recipient_fingerprint)\n        self.assertEqual(sign_result.returncode, 0)\n        sigs = self.gpg.list_keys(keys=recipient_fingerprint, sigs=True)[0]['sigs']\n        key_id = sigs[1][0]\n        self.assertIn(key_id, certifying_key.fingerprints[0])\n\n        # Revert our test environment changes\n        self.gpg.options.append('--debug-quick-random')\n\n\n    def test_encryption_and_decryption(self):\n        \"Test that encryption and decryption works\"\n        key = self.generate_key('Andrew', 'Able', 'alpha.com', passphrase='andy')\n        self.assertEqual(0, key.returncode, 'Non-zero return code')\n        andrew = key.fingerprint\n        key = self.generate_key('Barbara', 'Brown', 'beta.com')\n        self.assertEqual(0, key.returncode, 'Non-zero return code')\n        barbara = key.fingerprint\n        gpg = self.gpg\n        if gnupg._py3k:\n            data = 'Hello, André!'\n        else:  # pragma: no cover\n            data = unicode('Hello, André', gpg.encoding)\n        data = data.encode(gpg.encoding)\n        result = gpg.encrypt(data, barbara)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        self.assertNotEqual(data, edata, 'Data must have changed')\n        self.assertRaises(ValueError, gpg.decrypt, edata, passphrase='bbr\\x00own')\n        self.assertRaises(ValueError, gpg.decrypt, edata, passphrase='bbr\\rown')\n        self.assertRaises(ValueError, gpg.decrypt, edata, passphrase='bbr\\nown')\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        if data != ddata.data:  # pragma: no cover\n            logger.debug('was: %r', data)\n            logger.debug('new: %r', ddata.data)\n        self.assertEqual(data, ddata.data, 'Round-trip must work')\n        result = gpg.encrypt(data, [andrew, barbara])\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        self.assertNotEqual(data, edata, 'Data must have changed')\n        ddata = gpg.decrypt(edata, passphrase='andy')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        self.assertEqual(data, ddata.data, 'Round-trip must work')\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(data, ddata.data, 'Round-trip must work')\n        # Test with hidden recipients\n        result = gpg.encrypt(data, andrew, hidden_recipients=barbara)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        self.assertNotEqual(data, edata, 'Data must have changed')\n        ddata = gpg.decrypt(edata, passphrase='andy')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        self.assertEqual(data, ddata.data, 'Round-trip must work')\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(data, ddata.data, 'Round-trip must work')\n        # Test only hidden recipients\n        result = gpg.encrypt(data, None, hidden_recipients=[andrew, barbara])\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        self.assertNotEqual(data, edata, 'Data must have changed')\n        ddata = gpg.decrypt(edata, passphrase='andy')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        self.assertEqual(data, ddata.data, 'Round-trip must work')\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(data, ddata.data, 'Round-trip must work')\n        # Test with no recipients\n        self.assertRaises(ValueError, gpg.encrypt, data, None)\n        self.assertRaises(ValueError, gpg.encrypt, data, None, hidden_recipients=None)\n        self.assertRaises(ValueError, gpg.encrypt, data, None, hidden_recipients=None, symmetric=False)\n        # Test symmetric encryption\n        data = 'chippy was here'\n        self.assertRaises(ValueError, gpg.encrypt, data, None, passphrase='bbr\\x00own', symmetric=True)\n        self.assertRaises(ValueError, gpg.encrypt, data, None, passphrase='bbr\\rown', symmetric=True)\n        self.assertRaises(ValueError, gpg.encrypt, data, None, passphrase='bbr\\nown', symmetric=True)\n        result = gpg.encrypt(data, None, passphrase='bbrown', symmetric=True)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        self.assertEqual(data, str(ddata))\n        # Test symmetric encryption with non-default cipher\n        result = gpg.encrypt(data, None, passphrase='bbrown', symmetric='AES256')\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        self.assertEqual(data, str(ddata))\n        # Test that you can't encrypt with no recipients\n        self.assertRaises(ValueError, self.gpg.encrypt, data, [])\n        # Test extra_args parameter\n        result = gpg.encrypt(data, barbara, extra_args=['-z', '0'])\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(data.encode('ascii'), ddata.data, 'Round-trip must work')\n        # Test on_data functionality\n\n        chunks = []\n\n        def collector(data):\n            chunks.append(data)\n\n        gpg.on_data = collector\n        result = gpg.encrypt(data, barbara)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        self.assertIsNone(result.on_data_failure)\n        edata = str(result)\n        self.assertTrue(chunks)\n        expected = type(chunks[0])().join(chunks)\n        self.assertEqual(expected.decode('ascii'), edata)\n        chunks = []\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        self.assertEqual(data.encode('ascii'), ddata.data, 'Round-trip must work')\n        self.assertIsNone(result.on_data_failure)\n        expected = type(chunks[0])().join(chunks)\n        self.assertEqual(expected.decode('ascii'), data)\n\n        # test with on-data  generating an exception\n\n        def exceptor(data):\n            raise ValueError('exception in on_data')\n\n        chunks = []\n        gpg.on_data = exceptor\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertIs(type(ddata.on_data_failure), ValueError)\n        self.assertEqual(str(ddata.on_data_failure), 'exception in on_data')\n\n        # test signing with encryption and verification during decryption\n        logger.debug('encrypting with signature')\n        gpg.on_data = None\n        result = gpg.encrypt(data, barbara, sign=andrew, passphrase='andy')\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        edata = str(result)\n        logger.debug('decrypting with verification')\n        ddata = gpg.decrypt(edata, passphrase='bbrown')\n        self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n        self.assertEqual(data.encode('ascii'), ddata.data, 'Round-trip must work')\n        sig_values = list(ddata.sig_info.values())\n        self.assertTrue(sig_values)\n        sig_info = sig_values[0]\n        self.assertEqual(sig_info['fingerprint'], andrew)\n        logger.debug('decrypting with verification succeeded')\n\n    def test_import_and_export(self):\n        \"Test that key import and export works\"\n        self.test_list_keys_initial()\n        gpg = self.gpg\n        result = gpg.import_keys(KEYS_TO_IMPORT)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        self.assertTrue(bool(result))\n        self.assertEqual(result.summary(), '2 imported')\n        public_keys = gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 2), '2-element list expected')\n        private_keys = gpg.list_keys(secret=True)\n        self.assertEqual(0, private_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(private_keys, 0), 'Empty list expected')\n        ascii = gpg.export_keys([k['keyid'] for k in public_keys])\n        self.assertTrue(ascii.find('PGP PUBLIC KEY BLOCK') >= 0, 'Exported key should be public')\n        ascii = ascii.replace('\\r', '').strip()\n        match = compare_keys(ascii, KEYS_TO_IMPORT)\n        if match:  # pragma: no cover\n            logger.debug('was: %r', KEYS_TO_IMPORT)\n            logger.debug('now: %r', ascii)\n        self.assertEqual(0, match, 'Keys must match')\n        # Generate a key so we can test exporting private keys\n        key = self.do_key_generation()\n        if self.gpg.version < (2, 1):  # pragma: no cover\n            passphrase = None\n        else:\n            passphrase = 'bbrown'\n        ascii = gpg.export_keys(key.fingerprint, True, passphrase=passphrase)\n        self.assertTrue(isinstance(ascii, gnupg.text_type))\n        self.assertTrue(ascii.find('PGP PRIVATE KEY BLOCK') >= 0, 'Exported key should be private')\n        binary = gpg.export_keys(key.fingerprint, True, armor=False, passphrase=passphrase)\n        self.assertFalse(isinstance(binary, gnupg.text_type))\n        # import a secret key, and confirm that it's found in the list of\n        # secret keys.\n        result = gpg.import_keys(SECRET_KEY)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        self.assertEqual(result.summary(), '1 imported')\n        private_keys = gpg.list_keys(secret=True)\n        self.assertTrue(is_list_with_len(private_keys, 2))\n        found = False\n        for pk in private_keys:\n            if pk['keyid'].endswith('D2209820'):\n                found = True\n                break\n        self.assertTrue(found)\n        self.assertEqual(pk['uids'][0], 'Autogenerated Key <user1@test>')\n\n    def test_import_only(self):\n        \"Test that key import works\"\n        self.test_list_keys_initial()\n        result = self.gpg.import_keys(KEYS_TO_IMPORT)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        public_keys = self.gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 2), '2-element list expected')\n        private_keys = self.gpg.list_keys(secret=True)\n        self.assertEqual(0, private_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(private_keys, 0), 'Empty list expected')\n        ascii = self.gpg.export_keys([k['keyid'] for k in public_keys])\n        self.assertTrue(ascii.find('PGP PUBLIC KEY BLOCK') >= 0, 'Exported key should be public')\n        ascii = ascii.replace('\\r', '').strip()\n        match = compare_keys(ascii, KEYS_TO_IMPORT)\n        if match:  # pragma: no cover\n            logger.debug('was: %r', KEYS_TO_IMPORT)\n            logger.debug('now: %r', ascii)\n        self.assertEqual(0, match, 'Keys must match')\n\n    def test_signature_verification(self):\n        \"Test that signing and verification works\"\n        key = self.generate_key('Andrew', 'Able', 'alpha.com')\n        if gnupg._py3k:\n            data = 'Hello, André!'\n        else:  # pragma: no cover\n            data = unicode('Hello, André', self.gpg.encoding)\n        data = data.encode(self.gpg.encoding)\n        self.assertRaises(ValueError, self.gpg.sign, data, keyid=key.fingerprint, passphrase='bbr\\x00own')\n        self.assertRaises(ValueError, self.gpg.sign, data, keyid=key.fingerprint, passphrase='bbr\\rown')\n        self.assertRaises(ValueError, self.gpg.sign, data, keyid=key.fingerprint, passphrase='bbr\\nown')\n        sig = self.gpg.sign(data, keyid=key.fingerprint, passphrase='bbrown')\n        self.assertFalse(sig, 'Bad passphrase should fail')\n        sig = self.gpg.sign(data, keyid=key.fingerprint, passphrase='aable')\n        self.assertEqual(0, sig.returncode, 'Non-zero return code')\n        self.assertTrue(sig, 'Good passphrase should succeed')\n        if sig.username:  # pragma: no cover\n            # not set in recent versions of GnuPG e.g. 2.2.5\n            self.assertTrue(sig.username.startswith('Andrew Able'))\n        if sig.key_id:  # pragma: no cover\n            self.assertTrue(key.fingerprint.endswith(sig.key_id))\n        self.assertTrue(sig.hash_algo)\n        logger.debug('verification start')\n        verified = self.gpg.verify(sig.data)\n        self.assertEqual(0, verified.returncode, 'Non-zero return code')\n        logger.debug('verification end')\n        if key.fingerprint != verified.fingerprint:  # pragma: no cover\n            logger.debug('key: %r', key.fingerprint)\n            logger.debug('ver: %r', verified.fingerprint)\n        self.assertEqual(key.fingerprint, verified.fingerprint, 'Fingerprints must match')\n        self.assertEqual(verified.trust_level, verified.TRUST_ULTIMATE)\n        self.assertEqual(verified.trust_text, 'TRUST_ULTIMATE')\n        data_file = open(self.test_fn, 'rb')\n        sig = self.gpg.sign_file(data_file, keyid=key.fingerprint, passphrase='aable')\n        self.assertEqual(0, sig.returncode, 'Non-zero return code')\n        data_file.close()\n        self.assertTrue(sig, 'File signing should succeed')\n        self.assertTrue(sig.hash_algo)\n        try:\n            stream = gnupg._make_binary_stream(sig.data, self.gpg.encoding)\n            verified = self.gpg.verify_file(stream)\n        except UnicodeDecodeError:  # pragma: no cover\n            # sometimes happens in Python 2.6\n            from io import BytesIO\n            verified = self.gpg.verify_file(BytesIO(sig.data))\n        self.assertEqual(0, verified.returncode, 'Non-zero return code')\n        if key.fingerprint != verified.fingerprint:  # pragma: no cover\n            logger.debug('key: %r', key.fingerprint)\n            logger.debug('ver: %r', verified.fingerprint)\n        self.assertEqual(key.fingerprint, verified.fingerprint, 'Fingerprints must match')\n        data_file = open(self.test_fn, 'rb')\n        sig = self.gpg.sign_file(data_file, keyid=key.fingerprint, passphrase='aable', detach=True)\n        self.assertEqual(0, sig.returncode, 'Non-zero return code')\n        data_file.close()\n        self.assertTrue(sig, 'File signing should succeed')\n        self.assertTrue(sig.hash_algo)\n        try:\n            file = gnupg._make_binary_stream(sig.data, self.gpg.encoding)\n            verified = self.gpg.verify_file(file, self.test_fn)\n        except UnicodeDecodeError:  # pragma: no cover\n            # sometimes happens in Python 2.6\n            from io import BytesIO\n            verified = self.gpg.verify_file(BytesIO(sig.data))\n        self.assertEqual(0, verified.returncode, 'Non-zero return code')\n        if key.fingerprint != verified.fingerprint:  # pragma: no cover\n            logger.debug('key: %r', key.fingerprint)\n            logger.debug('ver: %r', verified.fingerprint)\n        self.assertEqual(key.fingerprint, verified.fingerprint, 'Fingerprints must match')\n        # Test in-memory verification\n        data_file = open(self.test_fn, 'rb')\n        data = data_file.read()\n        data_file.close()\n        fd, fn = tempfile.mkstemp(prefix='pygpg-test-')\n        os.write(fd, sig.data)\n        os.close(fd)\n        try:\n            verified = self.gpg.verify_data(fn, data)\n        finally:\n            os.remove(fn)\n        self.assertEqual(0, verified.returncode, 'Non-zero return code')\n        if key.fingerprint != verified.fingerprint:  # pragma: no cover\n            logger.debug('key: %r', key.fingerprint)\n            logger.debug('ver: %r', verified.fingerprint)\n        self.assertEqual(key.fingerprint, verified.fingerprint, 'Fingerprints must match')\n\n    def test_signature_file(self):\n        \"Test that signing and verification works via the GPG output\"\n        key = self.generate_key('Andrew', 'Able', 'alpha.com')\n        data_file = open(self.test_fn, 'rb')\n        sig_file = self.test_fn + '.asc'\n        sig = self.gpg.sign_file(data_file, keyid=key.fingerprint, passphrase='aable', detach=True, output=sig_file)\n        self.assertEqual(0, sig.returncode, 'Non-zero return code')\n        data_file.close()\n        self.assertTrue(sig, 'File signing should succeed')\n        self.assertTrue(sig.hash_algo)\n        self.assertTrue(os.path.exists(sig_file))\n        # Test in-memory verification\n        data_file = open(self.test_fn, 'rb')\n        data = data_file.read()\n        data_file.close()\n        try:\n            verified = self.gpg.verify_data(sig_file, data)\n        except Exception as e:\n            os.remove(sig_file)\n            self.fail(e)\n        self.assertTrue(verified.username.startswith('Andrew Able'))\n        self.assertTrue(key.fingerprint.endswith(verified.key_id))\n        self.assertEqual(0, verified.returncode, 'Non-zero return code')\n        if key.fingerprint != verified.fingerprint:  # pragma: no cover\n            logger.debug('key: %r', key.fingerprint)\n            logger.debug('ver: %r', verified.fingerprint)\n        self.assertEqual(key.fingerprint, verified.fingerprint, 'Fingerprints must match')\n        # Test file path verification\n        try:\n            verified = self.gpg.verify_file(sig_file, self.test_fn)\n        finally:\n            os.remove(sig_file)\n        self.assertTrue(verified.username.startswith('Andrew Able'))\n        self.assertTrue(key.fingerprint.endswith(verified.key_id))\n        self.assertEqual(0, verified.returncode, 'Non-zero return code')\n        if key.fingerprint != verified.fingerprint:  # pragma: no cover\n            logger.debug('key: %r', key.fingerprint)\n            logger.debug('ver: %r', verified.fingerprint)\n        self.assertEqual(key.fingerprint, verified.fingerprint, 'Fingerprints must match')\n\n    def test_subkey_signature_file(self):\n        \"Test that signing and verification works via the GPG output for subkeys\"\n        if self.gpg.version[0] < 2:  # pragma: no cover\n            raise unittest.SkipTest('Feature unavailable in GnuPG 1.x')\n        master_key = self.generate_key('Charlie', 'Clark', 'gamma.com', passphrase='123', with_subkey=False)\n        self.assertEqual(0, master_key.returncode, 'Non-zero return code')\n\n        subkey = self.gpg.add_subkey(master_key=master_key.fingerprint,\n                                     master_passphrase='123',\n                                     algorithm='dsa',\n                                     usage='sign',\n                                     expire=0)\n        self.assertEqual(0, subkey.returncode, 'Non-zero return code')\n\n        data_file = open(self.test_fn, 'rb')\n        sig_file = self.test_fn + '.asc'\n        sig = self.gpg.sign_file(data_file, keyid=subkey.fingerprint, passphrase='123', detach=True, output=sig_file)\n\n        self.assertEqual(0, sig.returncode, 'Non-zero return code')\n        data_file.close()\n        self.assertTrue(sig, 'File signing should succeed')\n        self.assertTrue(sig.hash_algo)\n        self.assertTrue(os.path.exists(sig_file))\n        # Test in-memory verification\n        data_file = open(self.test_fn, 'rb')\n        data = data_file.read()\n        data_file.close()\n        try:\n            verified = self.gpg.verify_data(sig_file, data)\n            self.assertTrue(verified.username.startswith('Charlie Clark'))\n            self.assertTrue(subkey.fingerprint.endswith(verified.key_id))\n        finally:\n            os.remove(sig_file)\n        self.assertEqual(0, verified.returncode, 'Non-zero return code')\n        if subkey.fingerprint != verified.fingerprint:  # pragma: no cover\n            logger.debug('key: %r', subkey.fingerprint)\n            logger.debug('ver: %r', verified.fingerprint)\n        self.assertEqual(subkey.fingerprint, verified.fingerprint, 'Fingerprints must match')\n\n    def test_deletion(self):\n        \"Test that key deletion works\"\n        result = self.gpg.import_keys(KEYS_TO_IMPORT)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        public_keys = self.gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 2), '2-element list expected')\n        result = self.gpg.delete_keys(public_keys[0]['fingerprint'])\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        public_keys = self.gpg.list_keys()\n        self.assertEqual(0, public_keys.returncode, 'Non-zero return code')\n        self.assertTrue(is_list_with_len(public_keys, 1), '1-element list expected')\n\n    def test_nogpg(self):\n        \"Test that absence of gpg is handled correctly\"\n        with self.assertRaises(OSError) as ar:\n            gnupg.GPG(gnupghome=self.homedir, gpgbinary='frob')\n        self.assertIn('frob', str(ar.exception))\n\n    def test_invalid_home(self):\n        \"Test that any specified gnupghome directory actually is one\"\n        hd = tempfile.mkdtemp(prefix='keys-')\n        shutil.rmtree(hd)  # make sure it isn't there anymore\n        with self.assertRaises(ValueError) as ar:\n            gnupg.GPG(gnupghome=hd)\n        self.assertTrue('gnupghome should be a directory' in str(ar.exception))\n\n    def test_make_args(self):\n        \"Test argument line construction\"\n        self.gpg.options = ['--foo', '--bar']\n        args = self.gpg.make_args(['a', 'b'], False)\n        self.assertTrue(len(args) > 4)\n        self.assertEqual(args[-4:], ['--foo', '--bar', 'a', 'b'])\n\n    def do_file_encryption_and_decryption(self, encfname, decfname):\n        \"Do the actual encryption/decryption test using given filenames\"\n        mode = None\n        if os.name == 'posix':\n            # pick a mode that won't be already in effect via umask\n            if os.path.exists(encfname) and os.path.exists(decfname):\n                mode = os.stat(encfname).st_mode | stat.S_IXUSR\n                os.chmod(encfname, mode)\n                # assume same for decfname\n                os.chmod(decfname, mode)\n        logger.debug('Encrypting to: %r', encfname)\n        logger.debug('Decrypting to: %r', decfname)\n        try:\n            key = self.generate_key('Andrew', 'Able', 'alpha.com', passphrase='andy')\n            self.assertEqual(0, key.returncode, 'Non-zero return code')\n            andrew = key.fingerprint\n            key = self.generate_key('Barbara', 'Brown', 'beta.com')\n            self.assertEqual(0, key.returncode, 'Non-zero return code')\n            barbara = key.fingerprint\n            data = 'Hello, world!'\n            stream = gnupg._make_binary_stream(data, self.gpg.encoding)\n            edata = self.gpg.encrypt_file(stream, [andrew, barbara], armor=False, output=encfname)\n            self.assertEqual(0, edata.returncode, 'Non-zero return code')\n            efile = open(encfname, 'rb')\n            ddata = self.gpg.decrypt_file(efile, passphrase='bbrown', output=decfname)\n            self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n            efile.seek(0, os.SEEK_SET)\n            edata = efile.read()\n            efile.close()\n            self.assertTrue(os.path.exists(decfname))\n            dfile = open(decfname, 'rb')\n            ddata = dfile.read()\n            dfile.close()\n            data = data.encode(self.gpg.encoding)\n            if ddata != data:  # pragma: no cover\n                logger.debug('was: %r', data)\n                logger.debug('new: %r', ddata)\n            self.assertEqual(data, ddata, 'Round-trip must work')\n\n            # Try opening the encrypted file in text mode (Issue #39)\n            # this doesn't fail in 2.x\n            if gnupg._py3k:\n                logger.debug('about to pass text stream to decrypt_file')\n                with open(encfname, 'r') as efile:\n                    self.assertRaises(UnicodeDecodeError, self.gpg.decrypt_file, efile,\n                                      passphrase='bbrown', output=decfname)\n        finally:\n            for fn in (encfname, decfname):\n                if os.name == 'posix' and mode is not None:\n                    # Check that the file wasn't deleted, and that the\n                    # mode bits we set are still in effect\n                    self.assertEqual(os.stat(fn).st_mode, mode)\n                if os.path.exists(fn):\n                    os.remove(fn)\n\n    def test_file_encryption_and_decryption(self):\n        \"Test that encryption/decryption to/from file works\"\n        encfno, encfname = tempfile.mkstemp(prefix='pygpg-test-')\n        decfno, decfname = tempfile.mkstemp(prefix='pygpg-test-')\n        # On Windows, if the handles aren't closed, the files can't be deleted\n        os.close(encfno)\n        os.close(decfno)\n        self.do_file_encryption_and_decryption(encfname, decfname)\n\n    @skipIf(os.name == 'nt', 'Test not suitable for Windows')\n    def test_invalid_outputs(self):\n        \"Test encrypting to invalid output files\"\n        encfno, encfname = tempfile.mkstemp(prefix='pygpg-test-')\n        os.close(encfno)\n        os.chmod(encfname, 0o400)\n        cases = (\n            ('/dev/null/foo', 'encrypt: not a directory'),\n            (encfname, 'encrypt: permission denied'),\n        )\n        key = self.generate_key('Barbara', 'Brown', 'beta.com')\n        barbara = key.fingerprint\n        data = 'Hello, world!'\n        for badout, message in cases:\n            stream = gnupg._make_binary_stream(data, self.gpg.encoding)\n            try:\n                # On Ubuntu and pypy-2.7, you often get an IOError \"Broken pipe\"\n                # during the encrypt operation ...\n                edata = self.gpg.encrypt_file(stream, barbara, armor=False, output=badout)\n                self.assertEqual(2, edata.returncode, 'Unexpected return code')\n            except IOError:\n                pass\n            # on GnuPG 1.4, you sometimes don't get any FAILURE messages, in\n            # which case status will not be set\n            if edata.status:\n                self.assertEqual(edata.status, message)\n\n        # now try with custom error map, if available\n        if os.path.exists('messages.json'):\n            with open('messages.json') as f:\n                mdata = json.load(f)\n            messages = {}\n            for k, v in mdata.items():\n                messages[int(k, 16)] = v\n\n            self.gpg.error_map = messages\n\n            encfno, encfname = tempfile.mkstemp(prefix='pygpg-test-')\n            os.close(encfno)\n            os.chmod(encfname, 0o400)\n\n            try:\n                cases = (\n                    ('/dev/null/foo', 'encrypt: Not a directory'),\n                    (encfname, 'encrypt: Permission denied'),\n                )\n\n                for badout, message in cases:\n                    stream = gnupg._make_binary_stream(data, self.gpg.encoding)\n                    try:\n                        # On Ubuntu and pypy-2.7, you often get an IOError \"Broken pipe\"\n                        # during the encrypt operation ...\n                        edata = self.gpg.encrypt_file(stream, barbara, armor=False, output=badout)\n                        self.assertEqual(2, edata.returncode, 'Unexpected return code')\n                    except IOError:\n                        pass\n                    # on GnuPG 1.4, you sometimes don't get any FAILURE messages, in\n                    # which case status will not be set\n                    if edata.status:\n                        message = '%s (%s)' % (message, badout)\n                        self.assertIn(edata.status, message)\n            finally:\n                os.chmod(encfname, 0o700)\n                os.remove(encfname)\n\n    def test_filenames_with_spaces(self):  # See Issue #16\n        \"Test that filenames with spaces are correctly handled\"\n        d = tempfile.mkdtemp()\n        try:\n            encfname = os.path.join(d, 'encrypted file')\n            decfname = os.path.join(d, 'decrypted file')\n            self.do_file_encryption_and_decryption(encfname, decfname)\n        finally:\n            shutil.rmtree(d, ignore_errors=True)\n\n    # This test does nothing on CI because it often leads to failures due to\n    # external servers being down\n    def test_search_keys(self):  # pragma: no cover\n        \"Test that searching for keys works\"\n\n        if 'NO_EXTERNAL_TESTS' not in os.environ:\n            r = self.gpg.search_keys('<vinay_sajip@hotmail.com>')\n            self.assertEqual(0, r.returncode, 'Non-zero return code')\n            self.assertTrue(r)\n            self.assertTrue('Vinay Sajip <vinay_sajip@hotmail.com>' in r[0]['uids'])\n            r = self.gpg.search_keys('92905378')\n            self.assertEqual(0, r.returncode, 'Non-zero return code')\n            self.assertTrue(r)\n            self.assertTrue('Vinay Sajip <vinay_sajip@hotmail.com>' in r[0]['uids'])\n\n    def test_quote_with_shell(self):\n        \"Test shell quoting with a real shell\"\n        if os.name != 'posix':\n            return\n\n        from subprocess import PIPE, Popen\n\n        workdir = tempfile.mkdtemp()\n        try:\n            s = \"'\\\\\\\"; touch %s/foo #'\" % workdir\n            cmd = 'echo %s' % gnupg.shell_quote(s)\n            p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)\n            p.communicate()\n            self.assertEqual(p.returncode, 0)\n            files = os.listdir(workdir)\n            self.assertEqual(files, [])\n            fn = \"'ab?'\"\n            cmd = 'touch %s/%s' % (workdir, gnupg.shell_quote(fn))\n            p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)\n            p.communicate()\n            self.assertEqual(p.returncode, 0)\n            files = os.listdir(workdir)\n            self.assertEqual(files, [\"'ab?'\"])\n        finally:\n            shutil.rmtree(workdir, ignore_errors=True)\n\n    def disabled_test_signing_with_uid(self):  # pragma: no cover\n        \"Test that signing with uids works. On hold for now.\"\n        self.generate_key('Andrew', 'Able', 'alpha.com')\n        uid = self.gpg.list_keys(True)[-1]['uids'][0]\n        try:\n            signfile = open(self.test_fn, 'rb')\n            signed = self.gpg.sign_file(signfile, keyid=uid, passphrase='aable', detach=True)\n        finally:\n            signfile.close()\n        self.assertEqual(0, signed.returncode, 'Non-zero return code')\n        self.assertTrue(signed.data)\n\n    def test_doctest_import_keys(self):\n        \"\"\"\n        Because GnuPG 2.1 requires passphrases for exporting and deleting\n        secret keys, and because console-mode passphrase entry requires\n        configuration changes, doctests can't always be used. This test\n        replicates the original doctest for import_keys as a regular test.\n\n        >>> import shutil\n        >>> shutil.rmtree(\"keys\", ignore_errors=True)\n        >>> GPGBINARY = os.environ.get('GPGBINARY', 'gpg')\n        >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome=\"keys\")\n        >>> input = gpg.gen_key_input(name_email='user1@test', passphrase='pp1')\n        >>> result = gpg.gen_key(input)\n        >>> fp1 = result.fingerprint\n        >>> result = gpg.gen_key(input)\n        >>> fp2 = result.fingerprint\n        >>> pubkey1 = gpg.export_keys(fp1)\n        >>> seckey1 = gpg.export_keys(fp1, secret=True, passphrase='pp1')\n        >>> seckeys = gpg.list_keys(secret=True)\n        >>> pubkeys = gpg.list_keys()\n        >>> assert fp1 in seckeys.fingerprints\n        >>> assert fp1 in pubkeys.fingerprints\n        >>> str(gpg.delete_keys(fp1))\n        'Must delete secret key first'\n        >>> str(gpg.delete_keys(fp1, secret=True, passphrase='pp1'))\n        'ok'\n        >>> str(gpg.delete_keys(fp1))\n        'ok'\n        >>> str(gpg.delete_keys(\"nosuchkey\"))\n        'No such key'\n        >>> seckeys = gpg.list_keys(secret=True)\n        >>> pubkeys = gpg.list_keys()\n        >>> assert not fp1 in seckeys.fingerprints\n        >>> assert not fp1 in pubkeys.fingerprints\n        >>> result = gpg.import_keys('foo')\n        >>> assert not result\n        >>> result = gpg.import_keys(pubkey1)\n        >>> pubkeys = gpg.list_keys()\n        >>> seckeys = gpg.list_keys(secret=True)\n        >>> assert not fp1 in seckeys.fingerprints\n        >>> assert fp1 in pubkeys.fingerprints\n        >>> result = gpg.import_keys(seckey1)\n        >>> assert result\n        >>> seckeys = gpg.list_keys(secret=True)\n        >>> pubkeys = gpg.list_keys()\n        >>> assert fp1 in seckeys.fingerprints\n        >>> assert fp1 in pubkeys.fingerprints\n        >>> assert fp2 in pubkeys.fingerprints\n        \"\"\"\n        gpg = self.gpg\n        inp = gpg.gen_key_input(name_email='user1@test', passphrase='pp1')\n        result = gpg.gen_key(inp)\n        fp1 = result.fingerprint\n        inp = gpg.gen_key_input(name_email='user2@test', passphrase='pp2')\n        result = gpg.gen_key(inp)\n        self.assertEqual(0, result.returncode, 'Non-zero return code')\n        fp2 = result.fingerprint\n        pubkey1 = gpg.export_keys(fp1)\n        self.assertTrue(pubkey1)\n        if gpg.version >= (2, 1):\n            passphrase = 'pp1'\n        else:  # pragma: no cover\n            passphrase = None\n        seckey1 = gpg.export_keys(fp1, secret=True, passphrase=passphrase)\n        self.assertTrue(seckey1)\n        seckeys = gpg.list_keys(secret=True)\n        self.assertEqual(0, seckeys.returncode, 'Non-zero return code')\n        pubkeys = gpg.list_keys()\n        self.assertEqual(0, pubkeys.returncode, 'Non-zero return code')\n        for fp in (fp1, fp2):\n            for keys in (seckeys, pubkeys):\n                self.assertIn(fp, keys.fingerprints)\n        result = gpg.delete_keys(fp1)\n        self.assertEqual(2, result.returncode, 'Unexpected return code')\n        self.assertEqual(str(result), 'Must delete secret key first')\n        if gpg.version < (2, 1):  # pragma: no cover\n            # Doesn't work on 2.1, and can't use SkipTest due to having\n            # to support older Pythons\n            result = gpg.delete_keys(fp1, secret=True, passphrase=passphrase)\n            self.assertEqual(0, result.returncode, 'Non-zero return code')\n            self.assertEqual(str(result), 'ok')\n            result = gpg.delete_keys(fp1)\n            self.assertEqual(0, result.returncode, 'Non-zero return code')\n            self.assertEqual(str(result), 'ok')\n            result = gpg.delete_keys('nosuchkey')\n            self.assertEqual(2, result.returncode, 'Unexpected return code')\n            self.assertEqual(str(result), 'No such key')\n            seckeys = gpg.list_keys(secret=True)\n            self.assertEqual(0, seckeys.returncode, 'Non-zero return code')\n            pubkeys = gpg.list_keys()\n            self.assertEqual(0, pubkeys.returncode, 'Non-zero return code')\n            self.assertFalse(fp1 in seckeys.fingerprints)\n            self.assertFalse(fp1 in pubkeys.fingerprints)\n            result = gpg.import_keys('foo')\n            self.assertFalse(result)\n\n    def test_recv_keys_no_server(self):\n        result = self.gpg.recv_keys('foo.bar.baz', '92905378')\n        self.assertEqual(2, result.returncode, 'Unexpected return code')\n        self.assertEqual(result.summary(), '0 imported')\n\n    def test_invalid_fileobject(self):\n        # accidentally on purpose pass in a filename rather than the file itself\n        bad = b'foobar.txt'\n        with self.assertRaises((TypeError, ValueError)) as ec:\n            self.gpg.decrypt_file(bad, passphrase='', output='/tmp/decrypted.txt')\n        if gnupg._py3k:\n            expected = 'Not a valid file or path: %s' % bad\n        else:\n            expected = 'No such file: %s' % bad\n        self.assertEqual(str(ec.exception), expected)\n\n    def remove_all_existing_keys(self):\n        for root, dirs, files in os.walk(self.homedir):\n            for d in dirs:\n                p = os.path.join(root, d)\n                shutil.rmtree(p)\n            for f in files:\n                if f.endswith('.conf'):\n                    continue\n                p = os.path.join(root, f)\n                os.remove(p)\n\n    def test_no_such_key(self):\n        key = self.generate_key('Barbara', 'Brown', 'beta.com')\n        barbara = key.fingerprint\n        gpg = self.gpg\n        if gnupg._py3k:\n            data = 'Hello, André!'\n        else:  # pragma: no cover\n            data = unicode('Hello, André', gpg.encoding)\n        data = data.encode(gpg.encoding)\n        encrypted = gpg.encrypt(data, barbara)\n        self.remove_all_existing_keys()\n        decrypted = gpg.decrypt(str(encrypted), passphrase='bbrown')\n        self.assertFalse(decrypted.ok)\n        expected = {'decryption failed', 'no secret key', 'no data was provided'}\n        self.assertIn(decrypted.status, expected)\n\n    def test_get_recipients(self):\n        gpg = self.gpg\n        inp = gpg.gen_key_input(name_email='user1@test', passphrase='pp1')\n        key1 = gpg.gen_key(inp)\n        inp = gpg.gen_key_input(name_email='user2@test', passphrase='pp2')\n        key2 = gpg.gen_key(inp)\n        data = 'super secret'.encode(gpg.encoding)\n        edata = gpg.encrypt(data, (key1.fingerprint, key2.fingerprint))\n        logger.debug('Getting recipients')\n        ids = gpg.get_recipients(edata.data.decode(gpg.encoding))\n        self.assertGreater(len(ids), 0)\n        idlen = len(ids[0])\n        ids = set(ids)\n        expected = set((key1.fingerprint[-idlen:], key2.fingerprint[-idlen:]))\n        self.assertEqual(expected, ids)\n\n    def test_passing_paths(self):\n        key1 = self.generate_key('Andrew', 'Able', 'alpha.com', passphrase='andy')\n        self.assertEqual(0, key1.returncode, 'Non-zero return code')\n        andrew = key1.fingerprint\n        key2 = self.generate_key('Barbara', 'Brown', 'beta.com')\n        self.assertEqual(0, key2.returncode, 'Non-zero return code')\n        barbara = key2.fingerprint\n        data = b'Hello, world!'\n        fd, fn = tempfile.mkstemp(prefix='pygpg-test-')\n        os.write(fd, data)\n        os.close(fd)\n        gpg = self.gpg\n        try:\n            # Check encryption\n            edata = gpg.encrypt_file(fn, [andrew, barbara], armor=False)\n            self.assertEqual(0, edata.returncode, 'Non-zero return code')\n            self.assertEqual(edata.status, 'encryption ok')\n            with open(fn, 'wb') as f:\n                f.write(edata.data)\n            # Check getting recipients\n            ids = gpg.get_recipients_file(fn)\n            idlen = len(ids[0])\n            keys = gpg.list_keys()\n            expected = set(d['subkeys'][0][0][-idlen:] for d in keys)\n            self.assertEqual(set(ids), expected)\n            # Check decryption\n            ddata = gpg.decrypt_file(fn, passphrase='andy')\n            self.assertEqual(0, ddata.returncode, 'Non-zero return code')\n            self.assertEqual(ddata.status, 'decryption ok')\n            self.assertEqual(ddata.data, data)\n            # Check signing\n            with open(fn, 'wb') as f:\n                f.write(data)\n            sig = gpg.sign_file(fn, keyid=andrew, passphrase='andy', binary=True)\n            self.assertEqual(0, sig.returncode, 'Non-zero return code')\n            self.assertEqual(sig.status, 'signature created')\n            # Check verification\n            with open(fn, 'wb') as f:\n                f.write(sig.data)\n            verified = gpg.verify_file(fn)\n            self.assertEqual(0, verified.returncode, 'Non-zero return code')\n            self.assertEqual(verified.status, 'signature valid')\n            self.assertTrue(verified.valid)\n            # Check importing keys\n            with open(fn, 'wb') as f:\n                f.write(KEYS_TO_IMPORT.encode('ascii'))\n            result = gpg.import_keys_file(fn)\n            self.assertEqual(0, result.returncode, 'Non-zero return code')\n            self.assertEqual(result.imported, 2)\n        finally:\n            os.remove(fn)\n\n    def test_multiple_signatures(self):\n        gpg = self.gpg\n        key1 = self.generate_key('Andrew', 'Able', 'alpha.com')\n        key2 = self.generate_key('Barbara', 'Brown', 'beta.com')\n        data = b'signed data'\n        sig1 = gpg.sign(data, keyid=key1.fingerprint, passphrase='aable', detach=True)\n        sig2 = gpg.sign(data, keyid=key2.fingerprint, passphrase='bbrown', detach=True)\n        # Combine the signatures, then verify\n        fd, fn = tempfile.mkstemp(prefix='pygpg-test-')\n        os.write(fd, sig1.data)\n        os.write(fd, sig2.data)\n        os.close(fd)\n        try:\n            verified = self.gpg.verify_data(fn, data)\n            sig_info = verified.sig_info\n            self.assertEqual(len(sig_info), 2)\n            actual = set(d['fingerprint'] for d in sig_info.values())\n            expected = set((key1.fingerprint, key2.fingerprint))\n            self.assertEqual(actual, expected)\n        finally:\n            os.remove(fn)\n\n    def test_multiple_signatures_one_invalid(self):\n        gpg = self.gpg\n        key1 = self.generate_key('Andrew', 'Able', 'alpha.com')\n        key2 = self.generate_key('Barbara', 'Brown', 'beta.com')\n        data = b'signed data'\n        other_data = b'other signed data'\n        sig1 = gpg.sign(data, keyid=key1.fingerprint, passphrase='aable', detach=True)\n        sig2 = gpg.sign(other_data, keyid=key2.fingerprint, passphrase='bbrown', detach=True)\n        # Combine the signatures, then verify\n        fd, fn = tempfile.mkstemp(prefix='pygpg-test-')\n        os.write(fd, sig1.data)\n        os.write(fd, sig2.data)\n        os.close(fd)\n        try:\n            verified = self.gpg.verify_data(fn, data)\n            sig_info = verified.sig_info\n            self.assertEqual(len(sig_info), 1)\n            actual = set(d['fingerprint'] for d in sig_info.values())\n            expected = set([key1.fingerprint])\n            self.assertEqual(actual, expected)\n            problems = verified.problems\n            self.assertEqual(len(problems), 1)\n            d = problems[0]\n            self.assertEqual(d['status'], 'signature bad')\n            self.assertTrue(key2.fingerprint.endswith(d['keyid']))\n        finally:\n            os.remove(fn)\n\n    @skipIf('CI' not in os.environ, \"Don't test locally\")\n    def test_auto_key_locating(self):\n        # Let's hope ProtonMail doesn't change their key anytime soon\n        expected_fingerprint = '90E619A84E85330A692F6D81A655882018DBFA9D'\n        # expected_type = 'rsa2048'\n\n        actual = self.gpg.auto_locate_key('no-reply@protonmail.com')\n\n        self.assertEqual(actual.fingerprint, expected_fingerprint)\n\n    def test_passphrase_encoding(self):\n        self.assertRaises(UnicodeEncodeError, self.gpg.decrypt, 'foo', passphrase=u'I’ll')\n\n    def test_configured_group(self):\n        # See issue #249\n        conf = 'group somegroup = BADF00D15BAD\\n'\n        fn = os.path.join(self.homedir, 'gpg.conf')\n        with open(fn, 'w') as f:\n            f.write(conf)\n        gpg = gnupg.GPG(gnupghome=self.homedir, gpgbinary=GPGBINARY)\n        self.assertEqual(gpg.version, self.gpg.version)\n\n    def test_exception_propagation(self):\n        if sys.version_info[0] < 3:\n            raise unittest.SkipTest('python 2 is too loose with Unicode')\n        key = self.generate_key('Andrew', 'Able', 'alpha.com', passphrase='andy')\n        self.assertEqual(0, key.returncode, 'Non-zero return code')\n        andrew = key.fingerprint\n        stream = io.StringIO(u'Hello, world!')  # make the wrong type of stream\n        self.assertRaises(TypeError, self.gpg.encrypt_file, stream, [andrew], armor=False)\n\n\nTEST_GROUPS = {\n    'sign':\n    set(['test_signature_verification', 'test_signature_file', 'test_subkey_signature_file']),\n    'crypt':\n    set([\n        'test_encryption_and_decryption', 'test_file_encryption_and_decryption', 'test_filenames_with_spaces',\n        'test_invalid_outputs', 'test_no_such_key'\n    ]),\n    'key':\n    set([\n        'test_deletion', 'test_import_and_export', 'test_list_keys_after_generation', 'test_list_signatures',\n        'test_key_generation_with_invalid_key_type', 'test_key_generation_with_escapes', 'test_key_generation_input',\n        'test_key_generation_with_colons', 'test_search_keys', 'test_scan_keys', 'test_scan_keys_mem',\n        'test_key_trust', 'test_add_subkey', 'test_add_subkey_with_invalid_key_type', 'test_deletion_subkey',\n        'test_list_subkey_after_generation', 'test_quick_sign_key'\n    ]),\n    'import':\n    set(['test_import_only', 'test_doctest_import_keys']),\n    'basic':\n    set(['test_environment', 'test_list_keys_initial', 'test_nogpg', 'test_make_args', 'test_quote_with_shell']),\n    'test':\n    set(['test_add_subkey']),\n}\n\n\ndef suite(args=None):\n    if args is None:  # pragma: no cover\n        args = sys.argv[1:]\n    if not args:\n        result = unittest.TestLoader().loadTestsFromTestCase(GPGTestCase)\n    else:  # pragma: no cover\n        tests = set()\n        for arg in args:\n            if arg in TEST_GROUPS:\n                tests.update(TEST_GROUPS[arg])\n            else:\n                print('Ignoring unknown test group %r' % arg)\n        result = unittest.TestSuite(list(map(GPGTestCase, tests)))\n    return result\n\n\ndef init_logging():\n    class PrimegenFilter(logging.Filter):\n        def filter(self, record):\n            arg = record.args\n            if isinstance(arg, (list, tuple)) and len(arg) > 0:\n                arg = arg[0]\n            return not arg or not isinstance(arg, unicode) or '[GNUPG:] PROGRESS primegen' not in arg\n\n    logging.basicConfig(level=logging.DEBUG,\n                        filename='test_gnupg.log',\n                        filemode='w',\n                        format='%(asctime)s %(levelname)-5s %(name)-10s '\n                        '%(threadName)-10s %(lineno)4d %(message)s')\n    logging.root.handlers[0].addFilter(PrimegenFilter())\n\n\ndef main():\n    init_logging()\n    logger.debug('Python version: %s', sys.version.replace('\\n', ' '))\n    adhf = argparse.ArgumentDefaultsHelpFormatter\n    ap = argparse.ArgumentParser(formatter_class=adhf, prog='test_gnupg')\n    aa = ap.add_argument\n    aa('-v', '--verbose', default=False, action='store_true', help='Increase verbosity')\n    options, args = ap.parse_known_args()\n    tests = suite(args)\n    verbosity = 2 if options.verbose else 1\n    results = unittest.TextTestRunner(verbosity=verbosity).run(tests)\n    failed = not results.wasSuccessful()\n    if failed and 'TOXENV' in os.environ and os.name != 'posix':  # pragma: no cover\n        os.system('type test_gnupg.log')\n    return failed\n\n\nif __name__ == '__main__':\n    sys.exit(main())\n"
  },
  {
    "path": "tox.ini",
    "content": "# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it, \"pip install tox\"\n# and then run \"tox\" from this directory.\n\n[tox]\nenvlist = py{27, 38, 39, 310, 311, 312, 313, 314, py, 27gpg2, 38gpg2, 39gpg2, 310gpg2, 311gpg2, 312gpg2, 313gpg2, 314gpg2, pygpg2}\nisolated_build = True\nrequires = virtualenv<20.22.0\n\n[testenv]\ncommands = {envpython} test_gnupg.py\n\nsetenv =\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n    STATIC_TEST_HOMEDIR\n\ndeps =\n\n\n[testenv:py27gpg2]\nenvdir = {toxinidir}/.tox/py27\nbasepython = python2.7\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py36gpg2]\nenvdir = {toxinidir}/.tox/py36\nbasepython = python3.6\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py37gpg2]\nenvdir = {toxinidir}/.tox/py37\nbasepython = python3.7\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py38gpg2]\nenvdir = {toxinidir}/.tox/py38\nbasepython = python3.8\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py39gpg2]\nenvdir = {toxinidir}/.tox/py39\nbasepython = python3.9\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py310gpg2]\nenvdir = {toxinidir}/.tox/py310\nbasepython = python3.10\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py311gpg2]\nenvdir = {toxinidir}/.tox/py311\nbasepython = python3.11\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py312gpg2]\nenvdir = {toxinidir}/.tox/py312\nbasepython = python3.12\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\n[testenv:py313gpg2]\nenvdir = {toxinidir}/.tox/py313\nbasepython = python3.13\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:py314gpg2]\nenvdir = {toxinidir}/.tox/py314\nbasepython = python3.14\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n\n[testenv:pypygpg2]\nenvdir = {toxinidir}/.tox/pypy\nbasepython = pypy\nsetenv =\n    LD_LIBRARY_PATH=/home/vinay/tmp/lib\n    GPGBINARY=gpg2\n    NO_EXTERNAL_TESTS=1\n\npassenv =\n    HOME\n    ENABLE_TOFU\n"
  }
]