Full Code of vsajip/python-gnupg for AI

master f6f172164ebb cached
29 files
314.2 KB
83.6k tokens
187 symbols
1 requests
Download .txt
Showing preview only (326K chars total). Download the full file or copy to clipboard to get everything.
Repository: vsajip/python-gnupg
Branch: master
Commit: f6f172164ebb
Files: 29
Total size: 314.2 KB

Directory structure:
gitextract_4ty4owtm/

├── .coveragerc
├── .flake8
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       └── python-package.yml
├── .gitignore
├── .hgignore
├── .hgtags
├── .readthedocs.yaml
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── _static/
│   │   └── sidebar.js
│   ├── _templates/
│   │   └── page.html
│   ├── conf.py
│   ├── index.rst
│   ├── requirements.txt
│   └── spelling_wordlist.txt
├── gnupg.py
├── messages.json
├── package.json
├── pyproject.toml
├── release
├── setup.cfg
├── test_gnupg.py
├── test_pubring.gpg
├── test_secring.gpg
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .coveragerc
================================================
[run]
branch = True
omit =
    /opt/python/*

[report]
exclude_lines =
    pragma: no cover
    raise NotImplementedError


================================================
FILE: .flake8
================================================
[flake8]
max-line-length=120
ignore =
    E731
    W504

exclude =
    build
    .tox

per-file-ignores =
    docs/conf.py:E265,E401,E402


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve this library.
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Environment**
 - OS, including version
 - Version of this library
 - Version of GnuPG

**Additional information**
Add any other information about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/workflows/python-package.yml
================================================
name: Tests

on:
  push:
    branches: [ master ]
    paths-ignore:
      - 'LICENSE.*'
      - 'README.*'
      - '.github/ISSUE-TEMPLATE/**'
      - 'docs/**'
      - '.hgignore'
      - '.gitignore'

  pull_request:
    branches: [ master ]
    paths-ignore:
      - 'LICENSE.*'
      - 'README.*'
      - '.github/ISSUE-TEMPLATE/**'
      - 'docs/**'
      - '.hgignore'
      - '.gitignore'

  schedule:  # at 03:07 on day-of-month 7
    - cron: '7 3 7 * *'

env:
  FORCE_COLOR: 1
  PIP_DISABLE_PIP_VERSION_CHECK: 1

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.13t', '3.14', '3.14t', 'pypy-3.9']

    steps:
    - uses: actions/checkout@v6
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v6
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Windows-only dependencies
      run: |
        $env:PATH = "C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\ProgramData\chocolatey\bin"
        [Environment]::SetEnvironmentVariable("Path", $env:PATH, "Machine")
        choco install gnupg --version "2.4.8"
        echo "C:\Program Files (x86)\GnuPG\bin" >> $env:GITHUB_PATH
      if: ${{ matrix.os == 'windows-latest' }}
    - name: Test with unittest
      run: |
        gpg --version
        python test_gnupg.py -v
      env:
        NO_EXTERNAL_TESTS: 1
    - name: Report failure info
      if: ${{ failure() }}
      run: |
        cat test_gnupg.log
    - name: Test with coverage
      run: |
        pip install coverage
        coverage run --branch test_gnupg.py
        coverage xml
      env:
        NO_EXTERNAL_TESTS: 1
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v5
      with:
        flags: unittests
        files: coverage.xml


================================================
FILE: .gitignore
================================================
build
dist
keys
.tox
.egg-info
DETAILS
MANIFEST
random_binary_data
__pycache__
*.pyc
*.log
.dict-validwords
docs/themes/
docs/_build/
.idea/
.idea_modules/
*.iml
*.iws
*.ipr
.vscode/
.settings/
.venv/
venv/



================================================
FILE: .hgignore
================================================
(build|dist|keys|\.(tox|egg-info))/
(DETAILS|MANIFEST|random_binary_data|watcher.conf|hover.json)$
\.(pyc|log|dict-validwords|yapf)$
local.*\.(sh|cmd)
docs/themes/


================================================
FILE: .hgtags
================================================
afbe4e74cfc7fb79dde344499e8934c88874ef85 0.3.6
1979c07150aa6c7cb16b7f70db71f1b973153b93 0.3.7
7a54f558eb05e2b0ff25b51f430255d92312705c 0.3.8
1ab8db449e5b2b28b2d1f4c96677dd31050d296e 0.3.9
d18b8320539fc43cf406e829080d9fb72388f7ce 0.4.0
1fe9f4c3d9b3a7672a43beb8ee08ac3e8234710d 0.4.1
20a9a5727c11ea07188f99377c67d1bd937f4d7e 0.4.2
e0f2692d6539aca706b63dba22d900d2c70d59f8 0.4.3
c2dc6e154027ab8fc13eba8a440ea43568c37d8b 0.4.4
5cedc567072cead1415b24b7aa1ee74901c3f66f 0.4.4.1
79af87708a2338d1ec58b5ff32747a6dc32e5147 0.4.5
5eae1f2c1034f1eb3e5b29dece3a7006cd687733 0.4.6
2eae4d96f406b9f09b274b6a2457b0a87a1a894e 0.4.7
0a57c41eb34b01878fa6ac00a6f1afb2e1c4f6be 0.4.8
9e58092577ebf033bd8b95dc50905e518ff4a7ea 0.4.9
129c8fa7451a75c71f5e2fd54686896b266fefc8 0.5.0
1f1265fd99f1dda764dfa5ba3b4f34093203eaf5 0.5.1
f7d1effbb6e19cc233a139510c04f0f3aad83dd7 0.5.2
b0327e6c3ce295d2f5780af98e7aec0cf1862091 0.5.3
3efac76918850def676c20aa4bddb6e132adf1b6 0.5.4
1b77f5b12ad72096eb71fd94b2f767ffc565d3eb 0.5.5
2825bcb5914434854a11657563d829cfd9d06b67 0.5.6


================================================
FILE: .readthedocs.yaml
================================================
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the version of Python and other tools you might need
build:
  os: ubuntu-22.04
  tools:
    python: "3.11"

# Build documentation in the docs/ directory with Sphinx
sphinx:
  configuration: docs/conf.py

# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
  install:
  - requirements: docs/requirements.txt



================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2008-2022 by Vinay Sajip.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * The name(s) of the copyright holder(s) may not be used to endorse or
      promote products derived from this software without specific prior
      written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



================================================
FILE: MANIFEST.in
================================================
include LICENSE.txt
include README.rst
include test_gnupg.py
include messages.json
include test_*ring.gpg



================================================
FILE: README.rst
================================================
|badge1| |badge2| |badge3|

.. |badge1| image:: https://img.shields.io/github/actions/workflow/status/vsajip/python-gnupg/python-package.yml
   :alt: GitHub test status

.. |badge2| image:: https://img.shields.io/codecov/c/github/vsajip/python-gnupg
   :target: https://app.codecov.io/gh/vsajip/python-gnupg
   :alt: GitHub coverage status

.. |badge3| image:: https://img.shields.io/pypi/v/python-gnupg
   :target: https://pypi.org/project/python-gnupg/
   :alt: PyPI package


What is it?
===========

The GNU Privacy Guard (gpg, or gpg.exe on Windows) is a command-line program
which provides support for programmatic access via spawning a separate process
to run it and then communicating with that process from your program.

This project, ``python-gnupg``, implements a Python library which takes care
of the internal details and allows its users to generate and manage keys,
encrypt and decrypt data, and sign and verify messages.

Installation
============

Installing from PyPI
--------------------

You can install this package from the Python Package Index (pyPI) by running::

    pip install python-gnupg

.. important::
   There is at least one fork of this project, which was apparently created
   because an earlier version of this software used the ``subprocess`` module
   with ``shell=True``, making it vulnerable to shell injection. **This is no
   longer the case**.

   Forks may not be drop-in compatible with this software, so take care to use
   the correct version, as indicated in the ``pip install`` command above.


Installing from a source distribution archive
---------------------------------------------
To install this package from a source distribution archive, do the following:

1. Extract all the files in the distribution archive to some directory on your
   system.
2. In that directory, run ``pip install .``, referencing a suitable ``pip`` (e.g. one
   from a specific venv which you want to install to).
3. Optionally, run ``python test_gnupg.py`` to ensure that the package is
   working as expected.

Credits
=======

* The developers of the GNU Privacy Guard.
* The original version of this module was developed by Andrew Kuchling.
* It was improved by Richard Jones.
* It was further improved by Steve Traugott.

The present incarnation, based on the earlier versions, uses the ``subprocess``
module and so works on Windows as well as Unix/Linux platforms. It's not,
however, 100% backwards-compatible with earlier incarnations.

Change log
==========

.. note:: GCnn refers to an issue nn on Google Code.


0.5.7 (future)
--------------

Released: Not yet

0.5.6
-----

Released: 2025-12-31

* Fix #261: Ensure capability, fingerprint and keygrip are added to subkey_info.

* Set username in the result when Verify uses a signing key that has expired or been
  revoked. Thanks to Steven Galgano for the patch.

0.5.5
-----

Released: 2025-08-04

* Fix #249: Handle fetching GPG version when not the first item in the configuration.

* Fix #250: Capture uid info in a uid_map attribute of ScanKeys/ListKeys.

* Fix #255: Improve handling of exceptions raised in background threads.


0.5.4
-----

Released: 2025-01-07

* Fix #242: Handle exceptions in ``on_data`` callable.


0.5.3
-----

Released: 2024-09-20

* Fix #117: Add WKD (Web Key Directory) support for auto-locating keys. Thanks to Myzel394
  for the patch.

* Fix #237: Ensure local variable is initialized even when an exception occurs.

* Fix #239: Remove logging of decryption result.


0.5.2
-----

Released: 2023-12-12

* Fix #228: Clarify documentation for encryption/decryption.

* Make I/O buffer size configurable via ``buffer_size`` attribute on a ``GPG`` instance.


0.5.1
-----

Released: 2023-07-22

* Added ``TRUST_EXPIRED`` to ``trust_keys``. Thanks to Leif Liddy for the patch.

* Fix #206: Remove deprecated ``--always-trust`` in favour of ``--trust-model always``

* Fix #208: Add ``status_detail`` attribute to result objects which is populated when
  the status is ``'invalid recipient'`` (encryption/decryption) or ``'invalid signer'``
  (signing). This attribute will be set when the result object's ``status`` attribute is
  set to ``invalid recipient`` and will contain more information about the failure in the
  form of ``reason:ident`` where ``reason`` is a text description of the reason, and
  ``ident`` identifies the recipient key.

* Add ``scan_keys_mem()`` function to scan keys in a string. Thanks to Sky Moore
  for the patch.

* Fix #214: Handle multiple signatures when one of them is invalid or unverified.

* A ``problems`` attribute was added which holds problems reported by ``gpg``
  during verification. This is a list of dictionaries, one for each reported
  problem. Each dictionary will have ``status`` and ``keyid`` keys indicating
  the problem and the corresponding key; other information in the dictionaries
  will be error specific.

* Fix #217: Use machine-readable interface to query the ``gpg`` version. Thanks to Justus
  Winter for the patch.

* Added the ability to export keys to a file. Thanks to Leif Liddy for the patch.


0.5.0
-----

Released: 2022-08-23

* Fixed #181: Added the ability to pass file paths to encrypt_file, decrypt_file,
  sign_file, verify_file, get_recipients_file and added import_keys_file.

* Fixed #183: Handle FAILURE and UNEXPECTED conditions correctly. Thanks to sebbASF for
  the patch.

* Fixed #185: Handle VALIDSIG arguments more robustly.

* Fixed #188: Remove handling of DECRYPTION_FAILED from Verify code, as not required
  there. Thanks to sebbASF for the patch.

* Fixed #190: Handle KEY_CREATED more robustly.

* Fixed #191: Handle NODATA messages during verification.

* Fixed #196: Don't log chunk data by default, as it could contain sensitive
  information (during decryption, for example).

* Added the ability to pass an environment to the gpg executable. Thanks to Edvard
  Rejthar for the patch.


0.4.9
-----

Released: 2022-05-20

* Fixed #161: Added a status attribute to the returned object from gen_key() which
  is set to 'ok' if a key was successfully created, or 'key not created' if that
  was reported by gpg, or None in any other case.

* Fixed #164: Provided the ability to add subkeys. Thanks to Daniel Kilimnik for the
  feature request and patch.

* Fixed #166: Added keygrip values to the information collected when keys are listed.
  Thanks to Daniel Kilimnik for the feature request and patch.

* Fixed #173: Added extra_args to send_keys(), recv_keys() and search_keys() to allow
  passing options relating to key servers.

0.4.8
-----

Released: 2021-11-24

* Fixed #147: Return gpg's return code in all result instances.

* Fixed #152: Add check for invalid file objects.

* Fixed #157: Provide more useful status message when a secret key is absent.

* Fixed #158: Added a get_recipients() API to find the recipients of an encrypted
  message without decrypting it.


0.4.7
-----

Released: 2021-03-11

* Fixed #129, #141: Added support for no passphrase during key generation.

* Fixed #143: Improved permission-denied test. Thanks to Elliot Cameron for the patch.

* Fixed #144: Updated logging to only show partial results.

* Fixed #146: Allowed a passphrase to be passed to import_keys(). Thanks to Chris de
  Graaf for the patch.


0.4.6
-----

Released: 2020-04-17

* Fixed #122: Updated documentation about gnupghome needing to be an existing
  directory.

* Fixed #123: Handled error conditions from gpg when calling trust_keys().

* Fixed #124: Avoided an exception being raised when ImportResult.summary()
  was called after a failed recv_keys().

* Fixed #128: Added ECC support by changing key generation parameters. (The Key-Length
  value isn't added if a curve is specified.)

* Fixed #130: Provided a mechanism to provide more complete error messages.

Support for Python versions 3.5 and under is discontinued, except for Python 2.7.


0.4.5
-----

Released: 2019-08-12

* Fixed #107: Improved documentation.

* Fixed #112: Raised a ValueError if a gnupghome is specified which is not an
  existing directory.

* Fixed #113: Corrected stale link in the documentation.

* Fixed #116: Updated documentation to clarify when spurious key-expired/
  signature-expired messages might be seen.

* Fixed #119: Added --yes to avoid pinentry when deleting secret keys with
  GnuPG >= 2.1.

* A warning is logged if gpg returns a non-zero return code.

* Added ``extra_args`` to ``import_keys``.

* Added support for CI using AppVeyor.


0.4.4
-----

Released: 2019-01-24

* Fixed #108: Changed how any return value from the ``on_data`` callable is
  processed. In earlier versions, the return value was ignored. In this version,
  if the return value is ``False``, the data received from ``gpg`` is not
  buffered. Otherwise (if the value is ``None`` or ``True``, for example), the
  data is buffered as normal. This functionality can be used to do your own
  buffering, or to prevent buffering altogether.

  The ``on_data`` callable is also called once with an empty byte-string to
  signal the end of data from ``gpg``.

* Fixed #97: Added an additional attribute ``check_fingerprint_collisions`` to
  ``GPG`` instances, which defaults to ``False``. It seems that ``gpg`` is happy
  to have duplicate keys and fingerprints in a keyring, so we can't be too
  strict. A user can set this attribute of an instance to ``True`` to trigger a
  check for collisions.

* Fixed #111: With GnuPG 2.2.7 or later, provide the fingerprint of a signing
  key for a failed signature verification, if available.

* Fixed #21: For verification where multiple signatures are involved, a
  mapping of signature_ids to fingerprint, keyid, username, creation date,
  creation timestamp and expiry timestamp is provided.

* Added a check to disallow certain control characters ('\r', '\n', NUL) in
  passphrases.


0.4.3
-----

Released: 2018-06-13

* Added --no-verbose to the gpg command line, in case verbose is specified in
  gpg.conf - we don't need verbose output.


0.4.2
-----

Released: 2018-03-28

* Fixed #81: Subkey information is now collected and returned in a ``subkey_info``
  dictionary keyed by the subkey's ID.

* Fixed #84: GPG2 version is now correctly detected on OS X.

* Fixed #94: Added ``expect_passphrase`` password for use on GnuPG >= 2.1 when
  passing passphrase to ``gpg`` via pinentry.

* Fixed #95: Provided a ``trust_keys`` method to allow setting the trust level
  for keys. Thanks to William Foster for a suggested implementation.

* Made the exception message when the gpg executable is not found contain the
  path of the executable that was tried. Thanks to Kostis Anagnostopoulos for
  the suggestion.

* Fixed #100: Made the error message less categorical in the case of a failure
  with an unspecified reason, adding some information from gpg error codes when
  available.


0.4.1
-----

Released: 2017-07-06

* Updated message handling logic to no longer raise exceptions when a message
  isn't recognised. Thanks to Daniel Kahn Gillmor for the patch.

* Always use always use ``--fixed-list-mode``, ``--batch`` and
  ``--with-colons``. Thanks to Daniel Kahn Gillmor for the patch.

* Improved ``scan_keys()`` handling on GnuPG >= 2.1. Thanks to Daniel Kahn
  Gillmor for the patch.

* Improved test behaviour with GnuPG >= 2.1. Failures when deleting test
  directory trees are now ignored. Thanks to Daniel Kahn Gillmor for the patch.

* Added ``close_file`` keyword argument to verify_file to allow the file closing
  to be made optional. Current behaviour is maintained - ``close_file=False``
  can be passed to skip closing the file being verified.

* Added the ``extra_args`` keyword parameter to allow custom arguments to be
  passed to the ``gpg`` executable.

* Instances of the ``GPG`` class now have an additional ``on_data`` attribute,
  which defaults to ``None``. It can be set to a callable which will be called
  with a single argument - a binary chunk of data received from the ``gpg``
  executable. The callable can do whatever it likes with the chunks passed to it
  - e.g. write them to a separate stream. The callable should not raise any
  exceptions (unless it wants the current operation to fail).


0.4.0
-----

Released: 2017-01-29

* Added support for ``KEY_CONSIDERED`` in more places - encryption /
  decryption, signing, key generation and key import.

* Partial fix for #32 (GPG 2.1 compatibility). Unfortunately, better
  support cannot be provided at this point, unless there are certain
  changes (relating to pinentry popups) in how GPG 2.1 works.

* Fixed #60: An IndexError was being thrown by ``scan_keys()``.

* Ensured that utf-8 encoding is used when the ``--with-column`` mode is
  used. Thanks to Yann Leboulanger for the patch.

* ``list_keys()`` now uses ``--fixed-list-mode``. Thanks to Werner Koch
  for the pointer.


0.3.9
-----

Released: 2016-09-10

* Fixed #38: You can now request information about signatures against
  keys. Thanks to SunDwarf for the suggestion and patch, which was used
  as a basis for this change.

* Fixed #49: When exporting keys, no attempt is made to decode the output when
  armor=False is specified.

* Fixed #53: A ``FAILURE`` message caused by passing an incorrect passphrase
  is handled.

* Handled ``EXPORTED`` and ``EXPORT_RES`` messages while exporting keys. Thanks
  to Marcel Pörner for the patch.

* Fixed #54: Improved error message shown when gpg is not available.

* Fixed #55: Added support for ``KEY_CONSIDERED`` while verifying.

* Avoided encoding problems with filenames under Windows. Thanks to Kévin
  Bernard-Allies for the patch.

* Fixed #57: Used a better mechanism for comparing keys.


0.3.8
-----

Released: 2015-09-24

* Fixed #22: handled ``PROGRESS`` messages during verification and signing.

* Fixed #26: handled ``PINENTRY_LAUNCHED`` messages during verification,
  decryption and key generation.

* Fixed #28: Allowed a default Name-Email to be computed even when neither of
  ``LOGNAME`` and ``USERNAME`` are in the environment.

* Fixed #29: Included test files missing from the tarball in previous versions.

* Fixed #39: On Python 3.x, passing a text instead of a binary stream caused
  file decryption to hang due to a ``UnicodeDecodeError``. This has now been
  correctly handled: The decryption fails with a "no data" status.

* Fixed #41: Handled Unicode filenames correctly by encoding them on 2.x using
  the file system encoding.

* Fixed #43: handled ``PINENTRY_LAUNCHED`` messages during key export. Thanks
  to Ian Denhardt for looking into this.

* Hide the console window which appears on Windows when gpg is spawned.
  Thanks to Kévin Bernard-Allies for the patch.

* Subkey fingerprints are now captured.

* The returned value from the ``list_keys`` method now has a new attribute,
  ``key_map``, which is a dictionary mapping key and subkey fingerprints to
  the corresponding key's dictionary. With this change, you don't need to
  iterate over the (potentially large) returned list to search for a key with
  a given fingerprint - the ``key_map`` dict will take you straight to the key
  info, whether the fingerprint you have is for a key or a subkey. Thanks to
  Nick Daly for the initial suggestion.

0.3.7
-----

Released: 2014-12-07

Signed with PGP key: Vinay Sajip (CODE SIGNING KEY) <vinay_sajip@yahoo.co.uk>

Key Fingerprint    : CA74 9061 914E AC13 8E66 EADB 9147 B477 339A 9B86

* Added an ``output`` keyword parameter to the ``sign`` and
  ``sign_file`` methods, to allow writing the signature to a file.
  Thanks to Jannis Leidel for the patch.

* Allowed specifying ``True`` for the ``sign`` keyword parameter,
  which allows use of the default key for signing and avoids having to
  specify a key id when it's desired to use the default. Thanks to
  Fabian Beutel for the patch.

* Used a uniform approach with subprocess on Windows and POSIX: shell=True
  is not used on either.

* When signing/verifying, the status is updated to reflect any expired or
  revoked keys or signatures.

* Handled 'NOTATION_NAME' and 'NOTATION_DATA' during verification.

* Fixed #1, #16, #18, #20: Quoting approach changed, since now shell=False.

* Fixed #14: Handled 'NEED_PASSPHRASE_PIN' message.

* Fixed #8: Added a scan_keys method to allow scanning of keys without the
  need to import into a keyring. Thanks to Venzen Khaosan for the suggestion.

* Fixed #5: Added '0x' prefix when searching for keys. Thanks to Aaron Toponce
  for the report.

* Fixed #4: Handled 'PROGRESS' message during encryption. Thanks to Daniel
  Mills for the report.

* Fixed #3: Changed default encoding to Latin-1.

* Fixed #2: Raised ValueError if no recipients were specified
  for an asymmetric encryption request.

* Handled 'UNEXPECTED' message during verification. Thanks to
  David Andersen for the patch.

* Replaced old range(len(X)) idiom with enumerate().

* Refactored ``ListKeys`` / ``SearchKeys`` classes to maximise use of common
  functions.

* Fixed GC94: Added ``export-minimal`` and ``armor`` options when exporting
  keys. This addition was inadvertently left out of 0.3.6.

0.3.6
-----

Released: 2014-02-05

* Fixed GC82: Enabled fast random tests on gpg as well as gpg2.
* Fixed GC85: Avoided deleting temporary file to preserve its permissions.
* Fixed GC87: Avoided writing passphrase to log.
* Fixed GC95: Added ``verify_data()`` method to allow verification of
  signatures in memory.
* Fixed GC96: Regularised end-of-line characters.
* Fixed GC98: Rectified problems with earlier fix for shell injection.

0.3.5
-----

Released: 2013-08-30

* Added improved shell quoting to guard against shell injection.
* Fixed GC76: Added ``search_keys()`` and ``send_keys()`` methods.
* Fixed GC77: Allowed specifying a symmetric cipher algorithm.
* Fixed GC78: Fell back to utf-8 encoding when no other could be determined.
* Fixed GC79: Default key length is now 2048 bits.
* Fixed GC80: Removed the Name-Comment default in key generation.

0.3.4
-----

Released: 2013-06-05

* Fixed GC65: Fixed encoding exception when getting version.
* Fixed GC66: Now accepts sets and frozensets where appropriate.
* Fixed GC67: Hash algorithm now captured in sign result.
* Fixed GC68: Added support for ``--secret-keyring``.
* Fixed GC70: Added support for multiple keyrings.

0.3.3
-----

Released: 2013-03-11

* Fixed GC57: Handled control characters in ``list_keys()``.
* Fixed GC61: Enabled fast random for testing.
* Fixed GC62: Handled ``KEYEXPIRED`` status.
* Fixed GC63: Handled ``NO_SGNR`` status.

0.3.2
-----

Released: 2013-01-17

* Fixed GC56: Disallowed blank values in key generation.
* Fixed GC57: Handled colons and other characters in ``list_keys()``.
* Fixed GC59/GC60: Handled ``INV_SGNR`` status during verification and removed
  calls requiring interactive password input from doctests.

0.3.1
-----

Released: 2012-09-01

* Fixed GC45: Allowed additional arguments to gpg executable.
* Fixed GC50: Used latin-1 encoding in tests when it's known to be required.
* Fixed GC51: Test now returns non-zero exit status on test failure.
* Fixed GC53: Now handles ``INV_SGNR`` and ``KEY_NOT_CREATED`` statuses.
* Fixed GC55: Verification and decryption now return trust level of signer in
  integer and text form.

0.3.0
-----

Released: 2012-05-12

* Fixed GC49: Reinstated Yann Leboulanger's change to support subkeys
  (accidentally left out in 0.2.7).

0.2.9
-----

Released: 2012-03-29

* Fixed GC36: Now handles ``CARDCTRL`` and ``POLICY_URL`` messages.
* Fixed GC40: Now handles ``DECRYPTION_INFO``, ``DECRYPTION_FAILED`` and
  ``DECRYPTION_OKAY`` messages.
* The ``random_binary_data file`` is no longer shipped, but constructed by the
  test suite if needed.

0.2.8
-----

Released: 2011-09-02

* Fixed GC29: Now handles ``IMPORT_RES`` while verifying.
* Fixed GC30: Fixed an encoding problem.
* Fixed GC33: Quoted arguments for added safety.

0.2.7
-----

Released: 2011-04-10

* Fixed GC24: License is clarified as BSD.
* Fixed GC25: Incorporated Daniel Folkinshteyn's changes.
* Fixed GC26: Incorporated Yann Leboulanger's subkey change.
* Fixed GC27: Incorporated hysterix's support for symmetric encryption.
* Did some internal cleanups of Unicode handling.

0.2.6
-----

Released: 2011-01-25

* Fixed GC14: Should be able to accept passphrases from GPG-Agent.
* Fixed GC19: Should be able to create a detached signature.
* Fixed GC21/GC23: Better handling of less common responses from GPG.

0.2.5
-----

Released: 2010-10-13

* Fixed GC11/GC16: Detached signatures can now be created.
* Fixed GC3: Detached signatures can be verified.
* Fixed GC12: Better support for RSA and IDEA.
* Fixed GC15/GC17: Better support for non-ASCII input.

0.2.4
-----

Released: 2010-03-01

* Fixed GC9: Now allows encryption without armor and the ability to encrypt
  and decrypt directly to/from files.

0.2.3
-----

Released: 2010-01-07

* Fixed GC7: Made sending data to process threaded and added a test case.
  With a test data file used by the test case, the archive size has gone up
  to 5MB (the size of the test file).

0.2.2
-----

Released: 2009-10-06

* Fixed GC5/GC6: Added ``--batch`` when specifying ``--passphrase-fd`` and
  changed the name of the distribution file to add the ``python-`` prefix.

0.2.1
-----

Released: 2009-08-07

* Fixed GC2: Added ``handle_status()`` method to the ``ListKeys`` class.

0.2.0
-----

Released: 2009-07-16

* Various changes made to support Python 3.0.

0.1.0
-----

Released: 2009-07-04

* Initial release.


================================================
FILE: docs/Makefile
================================================
# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
PAPER         =
BUILDDIR      = _build

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest remote apidocs

help:
	@echo "Please use \`make <target>' where <target> is one of"
	@echo "  html       to make standalone HTML files"
	@echo "  dirhtml    to make HTML files named index.html in directories"
	@echo "  singlehtml to make a single large HTML file"
	@echo "  pickle     to make pickle files"
	@echo "  json       to make JSON files"
	@echo "  htmlhelp   to make HTML files and a HTML help project"
	@echo "  qthelp     to make HTML files and a qthelp project"
	@echo "  devhelp    to make HTML files and a Devhelp project"
	@echo "  epub       to make an epub"
	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
	@echo "  text       to make text files"
	@echo "  man        to make manual pages"
	@echo "  changes    to make an overview of all changed/added/deprecated items"
	@echo "  linkcheck  to check all external links for integrity"
	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"

clean:
	-rm -rf $(BUILDDIR)/*

html:
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

apidocs:
	docfrag --libs .. gnupg -f hovertip > hover.json

remote:
	rsync -avz $(BUILDDIR)/html/* vopal:~/apps/rdc_docs/python-gnupg

spelling:
	$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)

dirhtml:
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."

singlehtml:
	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
	@echo
	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."

pickle:
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
	@echo
	@echo "Build finished; now you can process the pickle files."

json:
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
	@echo
	@echo "Build finished; now you can process the JSON files."

htmlhelp:
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in $(BUILDDIR)/htmlhelp."

qthelp:
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
	@echo
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Distlib.qhcp"
	@echo "To view the help file:"
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Distlib.qhc"

devhelp:
	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
	@echo
	@echo "Build finished."
	@echo "To view the help file:"
	@echo "# mkdir -p $$HOME/.local/share/devhelp/Distlib"
	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Distlib"
	@echo "# devhelp"

epub:
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
	@echo
	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."

latex:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
	@echo "Run \`make' in that directory to run these through (pdf)latex" \
	      "(use \`make latexpdf' here to do that automatically)."

latexpdf:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through pdflatex..."
	make -C $(BUILDDIR)/latex all-pdf
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

text:
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
	@echo
	@echo "Build finished. The text files are in $(BUILDDIR)/text."

man:
	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
	@echo
	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."

changes:
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
	@echo
	@echo "The overview file is in $(BUILDDIR)/changes."

linkcheck:
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in $(BUILDDIR)/linkcheck/output.txt."

doctest:
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
	@echo "Testing of doctests in the sources finished, look at the " \
	      "results in $(BUILDDIR)/doctest/output.txt."


================================================
FILE: docs/_static/sidebar.js
================================================
/*
 * sidebar.js
 * ~~~~~~~~~~
 *
 * This script makes the Sphinx sidebar collapsible.
 *
 * .sphinxsidebar contains .sphinxsidebarwrapper.  This script adds in
 * .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton used to
 * collapse and expand the sidebar.
 *
 * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden and the
 * width of the sidebar and the margin-left of the document are decreased.
 * When the sidebar is expanded the opposite happens.  This script saves a
 * per-browser/per-session cookie used to remember the position of the sidebar
 * among the pages.  Once the browser is closed the cookie is deleted and the
 * position reset to the default (expanded).
 *
 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 * :license: BSD, see LICENSE for details.
 *
 */

$(function() {
  // global elements used by the functions.
  // the 'sidebarbutton' element is defined as global after its
  // creation, in the add_sidebar_button function
  var bodywrapper = $('.bodywrapper');
  var sidebar = $('.sphinxsidebar');
  var sidebarwrapper = $('.sphinxsidebarwrapper');

  // original margin-left of the bodywrapper and width of the sidebar
  // with the sidebar expanded
  var bw_margin_expanded = bodywrapper.css('margin-left');
  var ssb_width_expanded = sidebar.width();

  // margin-left of the bodywrapper and width of the sidebar
  // with the sidebar collapsed
  var bw_margin_collapsed = '.8em';
  var ssb_width_collapsed = '.8em';

  // colors used by the current theme
  var dark_color = '#AAAAAA';
  var light_color = '#CCCCCC';

  function sidebar_is_collapsed() {
    return sidebarwrapper.is(':not(:visible)');
  }

  function toggle_sidebar() {
    if (sidebar_is_collapsed())
      expand_sidebar();
    else
      collapse_sidebar();
  }

  function collapse_sidebar() {
    sidebarwrapper.hide();
    sidebar.css('width', ssb_width_collapsed);
    bodywrapper.css('margin-left', bw_margin_collapsed);
    sidebarbutton.css({
        'margin-left': '0',
        'height': bodywrapper.height(),
        'border-radius': '5px'
    });
    sidebarbutton.find('span').text('»');
    sidebarbutton.attr('title', _('Expand sidebar'));
    document.cookie = 'sidebar=collapsed';
  }

  function expand_sidebar() {
    bodywrapper.css('margin-left', bw_margin_expanded);
    sidebar.css('width', ssb_width_expanded);
    sidebarwrapper.show();
    sidebarbutton.css({
        'margin-left': ssb_width_expanded-12,
        'height': bodywrapper.height(),
        'border-radius': '0 5px 5px 0'
    });
    sidebarbutton.find('span').text('«');
    sidebarbutton.attr('title', _('Collapse sidebar'));
    //sidebarwrapper.css({'padding-top':
    //  Math.max(window.pageYOffset - sidebarwrapper.offset().top, 10)});
    document.cookie = 'sidebar=expanded';
  }

  function add_sidebar_button() {
    sidebarwrapper.css({
        'float': 'left',
        'margin-right': '0',
        'width': ssb_width_expanded - 28
    });
    // create the button
    sidebar.append(
      '<div id="sidebarbutton"><span>&laquo;</span></div>'
    );
    var sidebarbutton = $('#sidebarbutton');
    // find the height of the viewport to center the '<<' in the page
    var viewport_height;
    if (window.innerHeight)
 	  viewport_height = window.innerHeight;
    else
	  viewport_height = $(window).height();
    var sidebar_offset = sidebar.offset().top;
    var sidebar_height = Math.max(bodywrapper.height(), sidebar.height());
    sidebarbutton.find('span').css({
        'display': 'block',
        'position': 'fixed',
        'top': Math.min(viewport_height/2, sidebar_height/2 + sidebar_offset) - 10
    });

    sidebarbutton.click(toggle_sidebar);
    sidebarbutton.attr('title', _('Collapse sidebar'));
    sidebarbutton.css({
        'border-radius': '0 5px 5px 0',
        'color': '#444444',
        'background-color': '#CCCCCC',
        'font-size': '1.2em',
        'cursor': 'pointer',
        'height': sidebar_height,
        'padding-top': '1px',
        'padding-left': '1px',
        'margin-left': ssb_width_expanded - 12
    });

    sidebarbutton.hover(
      function () {
          $(this).css('background-color', dark_color);
      },
      function () {
          $(this).css('background-color', light_color);
      }
    );
  }

  function set_position_from_cookie() {
    if (!document.cookie)
      return;
    var items = document.cookie.split(';');
    for(var k=0; k<items.length; k++) {
      var key_val = items[k].split('=');
      var key = key_val[0];
      if (key == 'sidebar') {
        var value = key_val[1];
        if ((value == 'collapsed') && (!sidebar_is_collapsed()))
          collapse_sidebar();
        else if ((value == 'expanded') && (sidebar_is_collapsed()))
          expand_sidebar();
      }
    }
  }

  add_sidebar_button();
  var sidebarbutton = $('#sidebarbutton');
  set_position_from_cookie();
});


================================================
FILE: docs/_templates/page.html
================================================
{% extends "!page.html" %}

{% block body %}
{{ super() }}
<!-- div id="disqus_thread"></div>
<script type="text/javascript">
  /**
    * var disqus_identifier; [Optional but recommended: Define a unique identifier (e.g. post id or slug) for this thread]
    */
  (function() {
   var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
   dsq.src = 'http://python-distlib.disqus.com/embed.js';
   (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript=python-distlib">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">Comments powered by <span class="logo-disqus">Disqus</span></a -->

{% endblock %}
{% block footer %}
{{ super() }}
<!-- script type="text/javascript">
var disqus_shortname = 'python-distlib';
(function () {
  var s = document.createElement('script'); s.async = true;
  s.src = 'http://disqus.com/forums/python-distlib/count.js';
  (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
}());
</script -->
{% endblock %}



================================================
FILE: docs/conf.py
================================================
# -*- coding: utf-8 -*-
#
# GnuPG Wrapper for Python documentation build configuration file, created by
# sphinx-quickstart on Thu Jul 24 04:03:38 2008.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import datetime, os, sys

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration -----------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.doctest',
    'sphinx.ext.intersphinx',
    'sphinx.ext.todo',
    'sphinx.ext.coverage',
    #'sphinx.ext.napoleon',
    #'sphinx.ext.imgmath',
    'sphinx.ext.ifconfig',
    'sphinx.ext.viewcode',
    'sphinxcontrib.spelling'
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'Python Wrapper for GnuPG'
copyright = u'2008-%s, Vinay Sajip' % datetime.date.today().year

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from gnupg import __version__ as release  # , __date__ as today

version = '.'.join(release.split('.')[:2])
if '.dev' in release:
    today = datetime.date.today().strftime('%b %d, %Y')
else:
    today = today.split()[0][1:].split('-')
    today = '%s %s, %s' % (today[1], today[0], today[2])

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']

# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = False

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

spelling_lang = 'en_GB'
spelling_word_list_filename = 'spelling_wordlist.txt'

# -- Options for HTML output ---------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = os.environ.get('DOCS_THEME', 'default')

THEME_OPTIONS = {'sizzle': {}}

if html_theme == 'sizzle' and os.path.isfile('hover.json'):
    import json

    with open('hover.json', encoding='utf-8') as f:
        THEME_OPTIONS['sizzle']['custom_data'] = {'hovers': json.load(f)}

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
if html_theme in THEME_OPTIONS:
    html_theme_options = THEME_OPTIONS[html_theme]

#html_theme_options = {'collapsiblesidebar': True}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['themes']

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.

# html_sidebars = {
# '**': [
# 'localtoc.html', 'globaltoc.html',  'relations.html',
# 'sourcelink.html', 'searchbox.html'
# ],
# }

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
# html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'GnuPGWrapperforPythondoc'

# -- Options for LaTeX output --------------------------------------------------

# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'

# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
    ('index', 'GnuPGWrapperforPython.tex', u'GnuPG Wrapper for Python Documentation', u'Vinay Sajip', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Additional stuff for the LaTeX preamble.
#latex_preamble = ''

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output --------------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [('index', 'python-gnupg', u'python-gnupg Documentation', [u'Vinay Sajip'], 1)]

# -- Options for Epub output ---------------------------------------------------

# Bibliographic Dublin Core info.
epub_title = u'python-gnupg'
epub_author = u'Vinay Sajip'
epub_publisher = u'Vinay Sajip'
epub_copyright = u'2019, Vinay Sajip'

# The language of the text. It defaults to the language option
# or en if the language is not set.
#epub_language = ''

# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''

# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''

# A unique identification for the text.
#epub_uid = ''

# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []

# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []

# A list of files that should not be packed into the epub file.
#epub_exclude_files = []

# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3

# Allow duplicate toc entries.
#epub_tocdup = True

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'python': ('http://docs.python.org/', None)}


def skip_module_docstring(app, what, name, obj, options, lines):
    if (what, name) == ('module', 'distlib'):
        del lines[:]


def setup(app):
    app.connect('autodoc-process-docstring', skip_module_docstring)


================================================
FILE: docs/index.rst
================================================
.. GnuPG Wrapper for Python documentation master file, created by
   sphinx-quickstart on Thu Jul 02 16:14:12 2009.

###########################################
`python-gnupg` - A Python wrapper for GnuPG
###########################################

.. rst-class:: release-info

   .. list-table::
      :widths: auto
      :stub-columns: 1

      * - Release:
        - |release|
      * - Date:
        - |today|

.. module:: gnupg
   :synopsis: A Python wrapper for the GNU Privacy Guard (GnuPG)

.. moduleauthor:: Vinay Sajip <vinay_sajip@red-dove.com>
.. sectionauthor:: Vinay Sajip <vinay_sajip@red-dove.com>


.. toctree::
   :maxdepth: 4

The ``gnupg`` module allows Python programs to make use of the functionality provided
by the `GNU Privacy Guard <https://gnupg.org/>`_ (abbreviated GPG or GnuPG). Using
this module, Python programs can encrypt and decrypt data, digitally sign documents
and verify digital signatures, manage (generate, list and delete) encryption keys,
using Public Key Infrastructure (PKI) encryption technology based on OpenPGP.

This module is expected to be used with Python versions >= 3.6, or Python 2.7 for
legacy code. Install this module using ``pip install python-gnupg``. You can then use
this module in your own code by doing ``import gnupg`` or similar.

.. _fork-note:

.. note::
   There is at least one fork of this project, which was apparently created because an
   earlier version of this software used the ``subprocess`` module with
   ``shell=True``, making it vulnerable to shell injection. **This is no longer the
   case**.

   Forks may not be drop-in compatible with this software, so take care to use the
   correct version, as indicated in the ``pip install`` command above.

.. index:: Deployment

.. _deployment:

Deployment Requirements
=======================

Apart from a recent-enough version of Python, in order to use this module you need to
have access to a compatible version of the GnuPG executable. The system has been
tested with GnuPG v1.4.9 on Windows and Ubuntu. On a Linux platform, this will
typically be installed via your distribution's package manager (e.g. ``apt-get`` on
Debian/Ubuntu). Windows binaries are available `here
<ftp://ftp.gnupg.org/gcrypt/binary/>`_ -- use one of the ``gnupg-w32cli-1.4.x.exe``
installers for the simplest deployment options.

.. note::
   On Windows, it is *not* necessary to perform a full installation of GnuPG, using
   the standard installer, on each computer: it is normally sufficient to distribute
   only the executable, ``gpg.exe``, and a DLL which it depends on, ``iconv.dll``.
   These files do not need to be placed in system directories, nor are registry
   changes needed. The files need to be placed in a location such that implicit
   invocation will find them - such as the working directory of the application which
   uses the ``gnupg`` module, or on the system path if that is appropriate for your
   requirements. Alternatively, you can specify the full path to the ``gpg``
   executable. *Note, however, that if you want to use GnuPG 2.0, then this simple
   deployment approach may not work, because there are more dependent files which you
   have to ship. For this reason, our recommendation is to stick with GnuPG 1.4.x on
   Windows, unless you specifically need 2.0 features - in which case, you may have to
   do a full installation rather than just relying on a couple of files).*

   Recent versions of GnuPG (>= 2.1.x) introduce a number of changes:

   * By default, passphrases cannot be passed via streams to ``gpg`` unless the line
     ``allow-loopback-pinentry`` is added to ``gpg-agent.conf`` in the home directory
     used by ``gpg`` (this is also where the keyring files are kept). If that file
     does not exist, you will need to create it with that single line. Note that even
     with this configuration, some versions of GnuPG 2.1.x won't work as expected. In
     our testing, we found, for example, that the 2.1.11 executable shipped with
     Ubuntu 16.04 didn't behave helpfully, whereas a GnuPG 2.1.15 executable compiled
     from source on the same machine worked as expected.
   * To export secret keys, a passphrase must be provided.

.. index:: Acknowledgements

Acknowledgements
================

This module is based on an earlier version, ``GPG.py``, written by Andrew Kuchling.
This was further improved by Richard Jones, and then even further by Steve Traugott.
The ``gnupg`` module is derived from `Steve Traugott's module
<https://web.archive.org/web/20150310174851/http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py>`_
(the original site no longer exists - this link is to the Wayback Machine), and uses
Python's ``subprocess`` module to communicate with the GnuPG executable, which it uses
to spawn a subprocess to do the real work.

I've gratefully incorporated improvements contributed or suggested by:

* Paul Cunnane (detached signature support)
* Daniel Folkinshteyn (``recv_keys``, handling of subkeys and SIGEXPIRED, KEYEXPIRED
  while verifying, EXPKEYSIG, REVKEYSIG)
* Dmitry Gladkov (handle KEYEXPIRED when importing)
* Abdul Karim (keyring patch)
* Yann Leboulanger (handle ERRSIG and NO_PUBKEY while verifying, get subkeys)
* Kirill Yakovenko (RSA and IDEA support)
* Robert Leftwich (handle INV_SGNR, KEY_NOT_CREATED)
* Michal Niklas (Trust levels for signature verification)
* David Noël (``search_keys``, ``send_keys`` functionality)
* David Andersen (handle UNEXPECTED during verification)
* Jannis Leidel (output signature to a file)
* Venzen Khaosan (scan_keys functionality)
* Marcel Pörner (handle EXPORTED, EXPORT_RES)
* Kévin Bernard-Allies (handle filename encoding under Windows)
* Daniel Kahn Gillmor (various improvements which were released in 0.4.1)
* William Foster (trust_key patch)

and Google Code / BitBucket users

* dprovins (ListKeys handle_status)
* ernest0x (improved support for non-ASCII input)
* eyepulp (additional options for encryption/decryption)
* hysterix.is.slackin (symmetric encryption support)
* natureshadow (improved status handling when smart cards in use)
* SunDwarf (storing signatures against keys)

(If I've missed anyone from this list, please let me know.)


Before you Start
================

GnuPG works on the basis of a "home directory" which is used to store public and
private keyring files as well as a trust database. You need to identify in advance
which directory on the end-user system will be used as the home directory, as you will
need to pass this information to ``gnupg``.

.. index:: Getting started

Getting Started
===============

You interface to the GnuPG functionality through an instance of the ``GPG`` class::

    >>> gpg = gnupg.GPG(gnupghome='/path/to/home/directory')

If the home directory does not exist, a ValueError will be raised. Thereafter, all the
operations available are accessed via methods of this instance. If the ``gnupghome``
parameter is omitted, GnuPG will use whatever directory is the default (consult the
GnuPG documentation for more information on what this might be).

The :class:`GPG` constructor also accepts the following additional optional keyword
arguments:

gpgbinary (defaults to "gpg")
    The path to the ``gpg`` executable.
verbose (defaults to ``False``)
    Print information (e.g. the gpg command lines, and status messages returned by
    gpg) to the console. You don't generally need to set this option, since the module
    uses Python's ``logging`` package to provide more flexible functionality. The
    status messages from ``gpg`` are quite voluminous, especially during key generation.
use_agent (defaults to ``False``)
    If specified as True, the ``--use-agent`` parameter is passed to ``gpg``, asking it to
    use any in-memory GPG agent (which remembers your credentials).
keyring (defaults to ``None``)
    If specified, the value is used as the name of the keyring file. The default
    keyring is not used. A list of paths to keyring files can also be specified.
options (defaults to ``None``)
    If specified, the value should be a list of additional command-line options to
    pass to ``gpg``.
secret_keyring (defaults to ``None``)
    If specified, the value is used as the name of the secret keyring file. A list of
    paths to secret keyring files can also be specified. *Note that these files are
    not used by GnuPG >= 2.1.*
env  (defaults to ``None``)
    If specified, the value is used as the environment variables used when calling the GPG
    executable.

.. versionchanged:: 0.3.4
   The ``keyring`` argument can now also be a list of keyring filenames.

.. versionadded:: 0.3.4
   The ``secret_keyring`` argument was added. *Note that this argument is not used
   when working with GnuPG >= 2.1.*

.. note:: If you specify values in ``options``, make sure you don't specify values
   which will conflict with other values added by ``python-gnupg``. You should be familiar
   with GPG command-line arguments and how they affect GPG's operation.

.. versionchanged:: 0.3.7
   The default encoding was changed to ``latin-1``. In earlier versions, it was either
   ``locale.getpreferredencoding()`` or, failing that, ``sys.stdin.encoding``, and
   failing that, ``utf-8``.

.. versionadded:: 0.5.0
   The ``env`` argument was added.

If the ``gpgbinary`` executable cannot be found, a ``ValueError`` is raised in
:meth:`GPG.__init__`.

The low-level communication between the ``gpg`` executable and ``python-gnupg`` is in
terms of bytes, and ``python-gnupg`` tries to convert gpg's ``stderr`` stream to text
using an encoding. The default value of this is ``latin-1``, but you can override this
by setting the encoding name in the GPG instance's ``encoding`` attribute after
instantiation, like this::

    >>> gpg = gnupg.GPG(gnupghome='/path/to/home/directory')
    >>> gpg.encoding = 'utf-8'

.. note:: If you use the wrong encoding, you may get exceptions. The ``'latin-1'``
   encoding leaves bytes as-is and shouldn't fail with encoding/decoding errors,
   though it may not decode text correctly (so you may see odd characters in the
   decoding output). The ``gpg`` executable will use an output encoding based on your
   environment settings (e.g. environment variables, code page etc.) but defaults to
   latin-1.

From version 0.5.2 onwards, you can also control the buffer size for the I/O between
``gpg`` and ``python-gnupg`` by setting the ``buffer_size`` attribute on a GPG instance.
It defaults to 16K.

.. versionadded:: 0.5.2
   The ``buffer_size`` attribute was added.


.. index:: Key; management

Key Management
==============

The module provides functionality for generating (creating) keys, listing keys,
deleting keys, and importing and exporting keys.

.. index:: Key; generating

Generating keys
---------------

The first thing you typically want to do when starting with a PKI framework is to
generate some keys. You can do this using the :meth:`~gnupg.GPG.gen_key` method::

    >>> key = gpg.gen_key(input_data)

where ``input_data`` is a special command string which tells GnuPG the
parameters you want to use when creating the key. To make life easier, a helper
method :meth:`~gnupg.GPG.gen_key_input` is provided which takes keyword
arguments which allow you to specify individual parameters of the key, as in
the following example::

    >>> input_data = gpg.gen_key_input(key_type="RSA", key_length=1024)

Sensible defaults are provided for parameters which you don't specify, as shown in the
following table.

.. cssclass:: generic-table table-bordered table-responsive-sm table-striped mx-auto mb-3 colwidths-auto smaller hpad

+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+
| Parameter     | Keyword Argument | Default value           | Example values              | Meaning of parameter                        |
+===============+==================+=========================+=============================+=============================================+
| Key-Type      | key_type         | "RSA"                   | "RSA", "DSA"                | The type of the primary key to generate. It |
|               |                  |                         |                             | must be capable of signing.                 |
+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+
| Key-Length    | key_length       | 1024                    | 1024, 2048                  | The length of the primary key in bits.      |
+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+
| Name-Real     | name_real        | "Autogenerated Key"     | "Fred Bloggs"               | The real name of the user identity which    |
|               |                  |                         |                             | is represented by the key.                  |
+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+
| Name-Comment  | name_comment     | "Generated by gnupg.py" | "A test user"               | A comment to attach to the user id.         |
+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+
| Name-Email    | name_email       | <username>@<hostname>   | "fred.bloggs\@domain.com"   | An email address for the user.              |
+---------------+------------------+-------------------------+-----------------------------+---------------------------------------------+

If you don't specify any parameters, the values in the table above will be used with
the defaults indicated. There is a whole set of other parameters you can specify; see
`this GnuPG document <https://github.com/gpg/gnupg/blob/master/doc/DETAILS>`_ for more
details. While use of RSA keys is common (they can be used for both signing and
encryption), another popular option is to use a DSA primary key (for signing) together
with a secondary El-Gamal key (for encryption). For this latter option, you could
supply the following additional parameters:

.. cssclass:: generic-table table-bordered table-responsive-sm table-striped mx-auto mb-3 colwidths-auto smaller hpad

+---------------+------------------+--------------------------------+---------------------------------------------+
| Parameter     | Keyword Argument | Example values                 | Meaning of parameter                        |
+===============+==================+================================+=============================================+
| Subkey-Type   | subkey_type      | "RSA", "ELG-E"                 | The type of the secondary key to generate.  |
+---------------+------------------+--------------------------------+---------------------------------------------+
| Subkey-Length | subkey_length    | 1024, 2048                     | The length of the secondary key in bits.    |
+---------------+------------------+--------------------------------+---------------------------------------------+
| Expire-Date   | expire_date      | "2009-12-31", "365d", "3m",    | The expiration date for the primary and any |
|               |                  | "6w", "5y", "seconds=<epoch>", | secondary key. You can specify an ISO date, |
|               |                  | 0                              | A number of days/weeks/months/years, an     |
|               |                  |                                | epoch value, or 0 for a non-expiring key.   |
+---------------+------------------+--------------------------------+---------------------------------------------+
| Passphrase    | passphrase       | "secret"                       | The passphrase to use. If this parameter is |
|               |                  |                                | not specified, no passphrase is needed to   |
|               |                  |                                | access the key. *Passphrases using newlines |
|               |                  |                                | are not supported*. **Note that for GnuPG   |
|               |                  |                                | versions >= 2.1, a passphrase must be       |
|               |                  |                                | provided, unless extra steps are taken**:   |
|               |                  |                                | see the ``no_protection`` argument, below.  |
+---------------+------------------+--------------------------------+---------------------------------------------+
| %no-protection| no_protection    | False (the default), True      | If no passphrase is wanted for a key (which |
|               |                  |                                | might be the default for tests, say), or if |
|               |                  |                                | you want to use an empty string as a        |
|               |                  |                                | passphrase, then you should specify ``True``|
|               |                  |                                | for this parameter. Otherwise, and if you   |
|               |                  |                                | don't use pinentry to enter a passphrase,   |
|               |                  |                                | then GnuPG >= 2.1 will not allow this. It   |
|               |                  |                                | doesn't make sense to specify ``True`` if a |
|               |                  |                                | non-empty passphrase is being supplied.     |
+---------------+------------------+--------------------------------+---------------------------------------------+

A complete list of key generation parameters can be found in the GnuPG documentation
`here <https://www.gnupg.org/documentation/manuals/gnupg/Unattended-GPG-key-generation.html>`__.

.. versionadded:: 0.4.7
   The ``no_protection`` keyword argument was added.

Whatever keyword arguments you pass to :meth:`~gnupg.GPG.gen_key_input` (other
than ``no_protection``) will be converted to the parameters expected by GnuPG
by replacing underscores with hyphens and title-casing the result. You can of
course construct the parameters in your own dictionary ``params`` and then pass
it as follows::

    >>> input_data = gpg.gen_key_input(**params)


The ``no_protection`` argument, if `True`, will be used to generate a
`%no-protection` line which tells GnuPG that no protection with a
passphrase is desired.

The return value from :meth:`~gnupg.GPG.gen_key` is an object whose
`type` and `fingerprint` attributes indicate the type and fingerprint of the
created key. If no key was created, these will be `None`.

.. versionadded:: 0.4.9
   There is now also a `status` attribute to the returned object which will be `'ok'`
   if a key was created, `'key not created'` if that was reported by `gpg`, or `None`
   in other cases.

.. index::
    single: Key; Generating subkeys

Generating subkeys
^^^^^^^^^^^^^^^^^^

To generate a subkey for an already generated key use the
:meth:`~gnupg.GPG.add_subkey` method::

    >>> subkey = gpg.add_subkey(master_key) # same as gpg.add_subkey(master_key, None)
    >>> subkey = gpg.add_subkey(master_key, master_key_password)

The :meth:`~gnupg.GPG.add_subkey` method has some additional keyword
arguments:

* ``algorithm`` (defaulting to ``rsa``)
* ``usage`` (defaulting to ``encrypt``)
* ``expire`` (defaulting to ``-``)

The parameters are explained with every possible value in `this GnuPG documentation
<https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html>`_
under ``quick-add-key``.

If you use the default algorithm, you'll get the default key size, which  is dependent
upon the version of GnuPG that's used. If you want to specify the key size explicitly,
you can use values for ``algorithm`` incorporating both the algorithm itself and the
key size, as in the following examples.

.. code-block:: python

    gpg.add_subkey(..., algorithm='rsa2048')
    gpg.add_subkey(..., algorithm='rsa3072')
    gpg.add_subkey(..., algorithm='rsa4096')


.. versionadded:: 0.4.9
   The ``add_subkey`` method was added.

Specifying key usages
^^^^^^^^^^^^^^^^^^^^^

Keys can be used for some or all of encryption, signing or authentication.
These usages map onto flags assigned to a key - one or more of 'encrypt',
'sign' and 'auth'. By default, nothing is specified, which assigns all flags to
a key. But sometimes you may want to depart from this behaviour. For example,
if you create a subkey for encryption, then you probably don't want encryption
to be enabled for the master key. You can specify the flags associated with a
key by passing a ``key_usage`` keyword argument to
:meth:`~gnupg.GPG.gen_key_input` which provides one or more of the above flags
in a space or comma-separated string, as in these example:

.. code-block:: python

    gpg.gen_key_input(..., key_usage='sign')
    gpg.gen_key_input(..., key_usage='sign encrypt')
    gpg.gen_key_input(..., key_usage='sign, auth')

This corresponds to the ``usage`` parameter of :meth:`~gnupg.GPG.add_subkey`,
described earlier. Note that you still need to ensure that the key type of the
key being created is appropriate for the usages.

.. index::
    single: Key; elliptic curves
    single: ECC keys

Generating elliptic curve keys
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To generate keys with elliptic curves, pass a `key_curve` keyword parameter to
:meth:`~gnupg.GPG.gen_key_input` and omit `key_length`. For example,
`key_curve='cv25519'` or `key_type='ECDSA', key_curve='nistp384'`. Refer to
`GnuPG resources <https://wiki.gnupg.org/ECC>`_ to see which options are
supported. Note that you'll need GnuPG >= 2.1 for this to work.

Supplemental information on the aliases used for key types and curves is given
`here
<https://www.gnupg.org/documentation/manuals/gcrypt/ECC-key-parameters.html>`__.
You can use the curve type alias in the ``algorithm`` argument to
:meth:`~gnupg.GPG.add_subkey`, as in the following example.

.. code-block:: python

    input_data = gpg.gen_key_input(key_type='EDDSA', key_curve='ed25519' ...)
    master_key = gpg.gen_key(input_data)
    subkey = gpg.add_subkey(master_key.fingerprint, algorithm='cv25519' ...)

.. index::
    single: Key; performance issues
    single: Entropy

Performance Issues
^^^^^^^^^^^^^^^^^^

Key generation requires the system to work with a source of random numbers. Systems
which are better at generating random numbers than others are said to have higher
*entropy*. This is typically obtained from the system hardware; the GnuPG
documentation recommends that keys be generated *only* on a local machine (i.e. not
one being accessed across a network), and that keyboard, mouse and disk activity be
maximised during key generation to increase the entropy of the system.

Unfortunately, there are some scenarios - for example, on virtual machines which don't
have real hardware - where insufficient entropy causes key generation to be
*extremely* slow. If you come across this problem, you should investigate means of
increasing the system entropy. On virtualised Linux systems, this can often be
achieved by installing the ``rng-tools`` package. This is available at least on
RPM-based and APT-based systems (Red Hat/Fedora, Debian, Ubuntu and derivative
distributions).

.. index:: Key; exporting

Exporting keys
--------------

To export keys, use the :meth:`~gnupg.GPG.export_keys` method::

    >>> ascii_armored_public_keys = gpg.export_keys(keyids) # same as gpg.export_keys(keyids, False)
    >>> ascii_armored_private_keys = gpg.export_keys(keyids, True) # True => private keys

For the ``keyids`` parameter, you can use a sequence of anything which GnuPG itself
accepts to identify a key - for example, the keyid or the fingerprint could be used.
If you want to pass a single keyid, then you can just pass in a string which
identifies the key. If you pass an empty list in ``keyids``, all keys are exported.

The :meth:`~gnupg.GPG.export_keys` method has some additional keyword arguments:

* ``armor`` (defaulting to ``True``) - when ``True``, passes ``--armor`` to ``gpg``.
* ``minimal`` (defaulting to ``False``) - when ``True``, passes
  ``--export-options export-minimal`` to ``gpg``.
* ``passphrase`` - if specified, sends the specified passphrase to ``gpg``. For
  GnuPG >= 2.1, exporting secret keys requires a passphrase to be provided.
* ``expect_passphrase`` - defaults to ``True`` for backward compatibility. If the
  passphrase is to be passed to ``gpg`` via pinentry, you wouldn't pass it here - so
  specify ``expect_passphrase=False`` in that case. If you don't do that, and don't
  pass a passphrase, a ``ValueError`` will be raised.
* ``output`` - defaults to ``None``, but if specified, should be the pathname of a file
  to which the exported keys should be written.

.. versionadded:: 0.3.7
   The ``armor`` and ``minimal`` keyword arguments were added.

.. versionadded:: 0.4.0
   The ``passphrase`` keyword argument was added.

.. versionadded:: 0.4.2
   The ``expect_passphrase`` keyword argument was added.

.. versionadded:: 0.5.1
   The ``output`` keyword argument was added.

.. index:: Key; importing

.. index:: Key; receiving

Importing and receiving keys
----------------------------

To import keys, get the key data as an ASCII string, say ``key_data``. Then you
can call :meth:`~gnupg.GPG.import_keys` with it::

    >>> import_result = gpg.import_keys(key_data)

This will import all the keys in ``key_data``. The number of keys imported will be
available in ``import_result.count`` and the fingerprints of the imported keys will be
in ``import_result.fingerprints``.

In addition, ``extra_args`` and ``passphrase`` keyword parameter can be specified. If
provided, ``extra_args`` is treated as a list of additional arguments to pass to the
``gpg`` executable. If ``passphrase`` is specified, it is passed to ``gpgg`` for when
an imported secret key has a passphrase.

.. versionadded:: 0.4.5
   The ``extra_args`` keyword argument.

.. versionadded:: 0.4.7
   The ``passphrase`` keyword argument.

To import keys from a file, use :meth:`~gnupg.GPG.import_keys_file` instead::

    >>> import_result = gpg.import_keys_file(key_path)

This also takes the keyword arguments specified for :meth:`~gnupg.GPG.import_keys`.

.. versionadded:: 0.5.0
   The :meth:`~gnupg.GPG.import_keys_file` method.

To receive keys from a keyserver, use :meth:`~gnupg.GPG.recv_keys`::

    >>> import_result = gpg.recv_keys('server-name', 'keyid1', 'keyid2', ...)

This will fetch keys with all specified keyids and import them. Note that on Windows,
you may require helper programs such as ``gpg_hkp.exe``, distributed with GnuPG, to
successfully run ``recv_keys``. On Jython, security permissions may lead to failure of
``recv_keys``.

Note that when you import keys, you may get spurious "key expired" / "signature
expired" messages which are sent by ``gpg`` and collected by ``python-gnupg``. This
may happen, for example, if there are subkey expiry dates which have been extended, so
that the keys haven't actually expired, even when ``gpg`` sends messages that they
have. Make sure you just look at the ``count`` and ``fingerprints`` attributes to
identify the keys that were imported.

.. index:: Key; listing

Listing keys
------------

Now that we've seen how to generate, import and export keys, let's move on to
finding which keys we have in our keyrings. This is fairly straightforward
using the :meth:`~gnupg.GPG.list_keys` method::

    >>> public_keys = gpg.list_keys() # same as gpg.list_keys(False)
    >>> private_keys = gpg.list_keys(True) # True => private keys

The returned value from :meth:`~gnupg.GPG.list_keys` is a subclass of Python's
``list`` class. Each entry represents one key and is a Python dictionary which
contains useful information about the corresponding key.

The following entries are in the returned dictionary. Some of the key names are not
ideal for describing the values, but they have been left as is for backward
compatibility reasons. As `GnuPG documentation
<https://github.com/gpg/gnupg/blob/master/doc/DETAILS>`_ has improved, a better
understanding is possible of the information returned by ``gpg``.

.. cssclass:: generic-table table-bordered table-responsive-sm table-striped mx-auto mb-3 colwidths-auto smaller hpad

+-------------+--------------------------------------------------------------------------------+
| dict key    | dict value (all string values)                                                 |
+=============+================================================================================+
| type        | Type of key                                                                    |
+-------------+--------------------------------------------------------------------------------+
| trust       | The validity of the key                                                        |
+-------------+--------------------------------------------------------------------------------+
| length      | The length of the key in bits                                                  |
+-------------+--------------------------------------------------------------------------------+
| algo        | Public key algorithm                                                           |
+-------------+--------------------------------------------------------------------------------+
| keyid       | The key ID                                                                     |
+-------------+--------------------------------------------------------------------------------+
| date        | The creation date of the key in UTC as a Unix timestamp                        |
+-------------+--------------------------------------------------------------------------------+
| expires     | The expiry date of the key in UTC as a timestamp, if specified                 |
+-------------+--------------------------------------------------------------------------------+
| dummy       | Certificate serial number, UID hash or trust signature info                    |
+-------------+--------------------------------------------------------------------------------+
| ownertrust  | The level of owner trust for the key                                           |
+-------------+--------------------------------------------------------------------------------+
| uid         | The user ID                                                                    |
+-------------+--------------------------------------------------------------------------------+
| sig         | Signature class                                                                |
+-------------+--------------------------------------------------------------------------------+
| cap         | Key capabilities                                                               |
+-------------+--------------------------------------------------------------------------------+
| issuer      | Issuer information                                                             |
+-------------+--------------------------------------------------------------------------------+
| flag        | A flag field                                                                   |
+-------------+--------------------------------------------------------------------------------+
| token       | Token serial number                                                            |
+-------------+--------------------------------------------------------------------------------+
| hash        | Hash algorithm                                                                 |
+-------------+--------------------------------------------------------------------------------+
| curve       | Curve name for elliptic curve cryptography (ECC) keys                          |
+-------------+--------------------------------------------------------------------------------+
| compliance  | Compliance flags                                                               |
+-------------+--------------------------------------------------------------------------------+
| updated     | Last updated timestamp                                                         |
+-------------+--------------------------------------------------------------------------------+
| origin      | Origin of keys                                                                 |
+-------------+--------------------------------------------------------------------------------+
| keygrip     | Keygrip of keys (Note that you'll need GnuPG >= 2.1 for this to work.)         |
+-------------+--------------------------------------------------------------------------------+
| subkeys     | A list containing [keyid, type, fingerprint, keygrip] elements for each subkey |
+-------------+--------------------------------------------------------------------------------+
| subkey_info | A dictionary of subkey information keyed on subkey id                          |
+-------------+--------------------------------------------------------------------------------+

Depending on the version of ``gpg`` used, some of these keys may have the value
``'unavailable'``. The last two keys are provided by ``python-gnupg`` rather than
``gpg``.

For more information about the values in this dictionary, refer to the GnuPG
documentation linked above. (Note that that documentation is not terribly
user-friendly, but nevertheless it should be usable.)

The returned value from :meth:`~gnupg.GPG.list_keys` has an attribute ``uids``, which is a
list of userids associated with the listed keys, and an attribute ``fingerprints``, which
is a list of the key fingerprints associated with the listed keys.

.. versionadded:: 0.3.8
   The returned value from :meth:`~gnupg.GPG.list_keys` now has a new
   attribute, ``key_map``, which is a dictionary mapping key and subkey
   fingerprints to the corresponding key's dictionary. With this change, you
   don't need to iterate over the (potentially large) returned list to search
   for a key with a given fingerprint - the ``key_map`` dict will take you
   straight to the key info, whether the fingerprint you have is for a key or a
   subkey.

.. versionadded:: 0.3.8
   You can also list a subset of keys by specifying a ``keys=`` keyword
   argument to :meth:`~gnupg.GPG.list_keys` whose value is either a single
   string matching a key, or a list of strings matching multiple keys. In this
   case, the return value only includes matching keys.

.. versionadded:: 0.3.9
   A new ``sigs=`` keyword argument has been added to
   :meth:`~gnupg.GPG.list_keys`, defaulting to ``False``. If you specify true,
   the ``sigs`` entry in the key information returned will contain a list of
   signatures which apply to the key. Each entry in the list is a 3-tuple of
   (``keyid``, ``user-id``, ``signature-class``) where the ``signature-class``
   is as defined by RFC-4880_.

   It doesn't make sense to supply both ``secret=True`` *and* ``sigs=True`` (people
   can't sign your secret keys), so in case ``secret=True`` is specified, the
   ``sigs=`` value has no effect.

.. versionadded:: 0.4.1
   Instances of the ``GPG`` class now have an additional ``on_data`` attribute, which
   defaults to ``None``. It can be set to a callable which will be called with a
   single argument - a binary chunk of data received from the ``gpg`` executable. The
   callable can do whatever it likes with the chunks passed to it - e.g. write them to
   a separate stream. The callable should not raise any exceptions (unless it wants
   the current operation to fail).

.. versionadded:: 0.4.2
   Information on keys returned by :meth:`~gnupg.GPG.list_keys` or
   :meth:`~gnupg.GPG.scan_keys` now incudes a ``subkey_info`` dictionary, which
   contains any returned information on subkeys such as creation and expiry
   dates. The dictionary is keyed on the subkey ID. The following additional
   keys are present in key information dictionaries: ``cap``, ``issuer``,
   ``flag``, ``token``, ``hash``, ``curve``, ``compliance``, ``updated`` and
   ``origin``.

.. versionadded:: 0.4.4
   Instances of the ``GPG`` class now have an additional
   ``check_fingerprint_collisions`` attribute, which defaults to ``False``. If set to
   a truthy value, fingerprint collisions are checked for (and a ``ValueError`` raised
   if a collision is detected) when listing or scanning keys. It appears that ``gpg``
   is quite lenient about allowing duplicated keys in keyrings, which would lead to
   collisions.

.. versionchanged:: 0.4.4
   The ``on_data`` callable will now be called with an empty chunk when the data
   stream from ``gpg`` is exhausted. It can now also return a value: if the value
   ``False`` is returned, the chunk will *not* be buffered within ``python-gnupg``.
   This might be useful if you want to do your own buffering or avoid buffering
   altogether. If any other value is returned (including the value ``None``, for
   backward compatibility) the chunk will be buffered as normal by ``python-gnupg``.

.. versionadded:: 0.4.6
   Instances of the ``GPG`` class now have an additional ``error_map`` attribute,
   which defaults to ``None``. If you set this, the value should be a dictionary
   mapping error codes to error messages. The source distribution includes a file
   ``messages.json`` which contains such a mapping, gleaned from the GnuPG library
   libgpg-error, version 1.37. The test suite shows how to convert that JSON to a form
   suitable for converting to an ``error_map`` value (basically, converting the string
   keys in the JSON to integers using base 16).

.. versionadded:: 0.4.9
   Information on keys returned by :meth:`~gnupg.GPG.list_keys` now includes
   the ``keygrip`` attribute. The ``subkeys`` attribute now also consists of four
   values with the ``keygrip`` being the fourth. Note that you'll need GnuPG >=
   2.1 for this to work.

.. versionadded:: 0.5.4
   Instances of the result classes from operations now have an ``on_data_failure``
   attribute, which defaults to ``None``. If an ``on_data`` callable raises an exception,
   the ``on_data_failure`` attribute of the returned object from a high-level operation
   is set to the first exception that was raised. The ``on_data`` callable will continue
   to be called with future chunks. If you use ``on_data`` with code that can raise any
   exceptions, be sure to check the ``on_data_failure`` attribute of a returned object
   before using any other aspects of the result.

.. versionadded:: 0.5.5
   The returned value from :meth:`~gnupg.GPG.list_keys` now has a new
   attribute, ``uid_map``, which is a dictionary mapping uids to dicts with
   detailed information about the corresponding uid. The keys of the information
   provided are listed in the table above. Refer to the `GnuPG documentation
   <https://github.com/gpg/gnupg/blob/master/doc/DETAILS>`_ for more information.

   You could use this information as in the following example:

    .. code-block:: pycon

        >>> from pprint import pprint
        >>> keys = gpg.list_keys()
        >>> pprint(keys.uids)
        ['Andrew Able (A test user) <andrew.able@alpha.com>',
         'Barb Bruin <barb.bruin@beta.com>',
         'Babs Broon <babs.broon@beta.com>',
         'Barbara Brown (A test user) <barbara.brown@beta.com>',
         'Charlie Clark (A test user) <charlie.clark@gamma.com>',
         'Donna Davis (A test user) <donna.davis@delta.com>']
        >>> pprint(keys.uid_map('Barbara Brown (A test user) <barbara.brown@beta.com>')
        {'algo': '',
         'date': '1739485458',
         'dummy': '8B989767967370B894C53279A3BDF655F00CD4DE',
         'expires': '',
         'keyid': '',
         'length': '',
         'ownertrust': '',
         'sig': '',
         'trust': 'u',
         'type': 'uid',
         'uid': 'Barbara Brown (A test user) <barbara.brown@beta.com>'}
        >>> pprint(keys.uid_map['Barb Bruin <barb.bruin@beta.com>'])
        {'algo': '',
         'date': '1739485886',
         'dummy': '951261047308BCA0B45FD738AD8630B336B88ECF',
         'expires': '',
         'keyid': '',
         'length': '',
         'ownertrust': '',
         'sig': '',
         'trust': 'u',
         'type': 'uid',
         'uid': 'Barb Bruin <barb.bruin@beta.com>'}
        >>> pprint(keys.uid_map['Babs Broon <babs.broon@beta.com>'])
        {'algo': '',
         'date': '',
         'dummy': '2BDB74660AC54DF33DE523429386E2D460904E74',
         'expires': '',
         'keyid': '',
         'length': '',
         'ownertrust': '',
         'sig': '',
         'trust': 'r',
         'type': 'uid',
         'uid': 'Babs Broon <babs.broon@beta.com>'}
        >>>

    The first two of these dictionaries show normal uids (trust is 'u', for ultimate), whereas the third
    shows a revoked uid (trust is 'r', for revoked).


.. _RFC-4880: https://tools.ietf.org/html/rfc4880#section-5.2.1


.. index:: Key; trusting

Setting the trust level for imported keys
-----------------------------------------

You can set the trust level for imported keys using
:meth:`~gnupg.GPG.trust_keys`::

    >>> gpg.trust_keys(fingerprints, trustlevel)

where the ``fingerprints`` are a list of fingerprints of keys for which the trust
level is to be set, and ``trustlevel`` is one of the string values ``'TRUST_EXPIRED'``,
``'TRUST_UNDEFINED'``, ``'TRUST_NEVER'``, ``'TRUST_MARGINAL'``, ``'TRUST_FULLY'`` or
``'TRUST_ULTIMATE'``.

You can also specify a single fingerprint for the ``fingerprints`` parameter.

.. versionadded:: 0.4.2
   The ``trust_keys`` method was added.

.. index:: Key; scanning

Scanning keys
-------------

We can also scan keys in files without importing them into a local keyring, by
using :meth:`~gnupg.GPG.scan_keys`::

    >>> keys = gpg.scan_keys(key_file_name)

The returned value from :meth:`~gnupg.GPG.scan_keys` has the same format as for
:meth:`~gnupg.GPG.list_keys`.

.. versionadded:: 0.3.7
   The :meth:`~gnupg.GPG.scan_keys` method was added.

To scan keys in a string, we can use :meth:`~gnupg.GPG.scan_keys_mem` instead::

    >>> keys = gpg.scan_keys_mem(key_text)

The result will be the same as for :meth:`~gnupg.GPG.scan_keys`.

.. versionadded:: 0.5.1
   The :meth:`~gnupg.GPG.scan_keys_mem` method was added.

.. index:: Key; deleting

Deleting keys
-------------

To delete keys, their key identifiers must be specified. If a public/private keypair
has been created, a private key needs to be deleted before the public key can be
deleted, and for both you use the :meth:`~gnupg.GPG.delete_keys` method::

    >>> key = gpg.gen_key(gpg.gen_key_input())
    >>> fp = key.fingerprint
    >>> str(gpg.delete_keys(fp)) # same as gpg.delete_keys(fp, False)
    'Must delete secret key first'
    >>> str(gpg.delete_keys(fp, True))# True => private keys
    'ok'
    >>> str(gpg.delete_keys(fp))
    'ok'
    >>> str(gpg.delete_keys("nosuchkey"))
    'No such key'

The argument you pass to :meth:`~gnupg.GPG.delete_keys` can be either a single
key identifier (e.g. keyid or fingerprint) or a sequence of key identifiers.

The :meth:`~gnupg.GPG.delete_keys` method has some additional keyword arguments:

* ``passphrase`` - if specified, sends the specified passphrase to ``gpg``. For GnuPG
  >= 2.1, exporting secret keys requires a passphrase to be provided.
* ``expect_passphrase`` - defaults to ``True`` for backward compatibility. If the
  passphrase is to be passed to ``gpg`` via pinentry, you wouldn't pass it here - so
  specify ``expect_passphrase=False`` in that case. If you don't do that, and don't
  pass a passphrase, a ``ValueError`` will be raised.
* ``exclamation_mode`` - defaults to ``False`` for backward compatibility. If the exclamation
  mode is set, and a fingerprint of a subkey is passed only that subkey will be deleted. If the
  fingerprint is of a primary key the entire key will be deleted.

.. versionadded:: 0.4.0
   The ``passphrase`` keyword argument was added.

.. versionadded:: 0.4.2
   The ``expect_passphrase`` keyword argument was added.

.. versionadded:: 0.4.9
   The ``exclamation_mode`` keyword argument was added.


.. index:: Key; searching

Searching for keys
------------------

You can search for keys by passing a search query and optionally a keyserver
name to the :meth:`~gnupg.GPG.search_keys`. If no keyserver is specified,
``pgp.mit.edu`` is used. A list of dictionaries describing keys that were found
is returned (this list could be empty). For example::

    >>> gpg.search_keys('vinay_sajip@hotmail.com', 'keyserver.ubuntu.com')
    [{'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'}]

.. versionadded:: 0.3.5
   The :meth:`~gnupg.GPG.search_keys` method was added.

.. index:: Key; sending

Sending keys
------------

You can send keys to a keyserver by passing its name and some key identifiers
to the :meth:`~gnupg.GPG.send_keys`. For example::

    >>> gpg.send_keys('keyserver.ubuntu.com', '6E4D5A2B')
    <gnupg.SendResult object at 0xb74d55ac>

.. versionadded:: 0.3.5
   The :meth:`~gnupg.GPG.send_keys` method was added.


Encryption and Decryption
=========================

Data intended for some particular recipients is encrypted with the public keys of
those recipients. Each recipient can decrypt the encrypted data using the
corresponding private key.

.. index:: Encryption

Encryption
----------

To encrypt a message, use the :meth:`~gnupg.GPG.encrypt` method::

    >>> encrypted_ascii_data = gpg.encrypt(data, recipients)

If you want to encrypt data in a file (or file-like object), use
:meth:`~gnupg.GPG.encrypt_file` instead::

    >>> encrypted_ascii_data = gpg.encrypt_file(stream, recipients) # e.g. after stream = open(filename, 'rb')

These methods both return an object such that:

* If encryption succeeded, the returned object's ``ok`` attribute is set to ``True``
  and the ``data`` attribute holds the encrypted data.
  Otherwise, the returned object's ``ok`` attribute is set to ``False`` and its
  ``status`` attribute (a message string) provides more information as to the reason
  for failure (for example, ``'invalid recipient'`` or ``'key expired'``).
* ``str(encrypted_ascii_data)`` gives the encrypted data in a non-binary format.

In both cases, ``recipients`` is a list of key fingerprints for those recipients. For
your convenience, if there is a single recipient, you can pass the fingerprint rather
than a 1-element array containing the fingerprint. Both methods accept the following
optional keyword arguments:

sign (defaults to ``None``)
    Either the Boolean value ``True``, or the fingerprint of a key which is used to
    sign the encrypted data. If ``True`` is specified, the default key is used for
    signing. When not specified, the data is not signed.
always_trust (defaults to ``False``)
    Skip key validation and assume that used keys are always fully trusted.
passphrase (defaults to ``None``)
    A passphrase to use when accessing the keyrings.
extra_args (defaults to ``None``)
    A list of additional arguments to pass to the ``gpg`` executable. For example, you
    could pass ``extra_args=['-z', '0']`` to disable compression, or you could pass
    ``extra_args=['--set-filename', 'name-to-embed-in-encrypted-file.txt']`` to embed
    a specific file name in the encrypted message.
armor (defaults to ``True``)
    Whether to use ASCII armor. If ``False``, binary data is produced.
output (defaults to ``None``)
    The name of an output file to write to. If a name is specified, the encrypted
    output is written directly to the file.

.. index:: Encryption; symmetric

symmetric (defaults to ``False``)
    If specified, symmetric encryption is used. In this case, specify recipients as
    ``None``. If ``True`` is specified, then the default cipher algorithm (``CAST5``)
    is used. Starting with version 0.3.5, you can also specify the cipher-algorithm to
    use (for example, ``'AES256'``). Check your ``gpg`` command line help to see what
    symmetric cipher algorithms are supported. Note that the default (``CAST5``) may
    not be the best available.

.. versionchanged:: 0.3.5
   A string can be passed for the ``symmetric`` argument, as well as ``True`` or
   ``False``. If a string is passed, it should be a symmetric cipher algorithm
   supported by the ``gpg`` you are using.

.. versionadded:: 0.4.1
   The ``extra_args`` keyword argument was added.

.. versionadded:: 0.5.1
   The ``status_detail`` attribute was added to the result object. This attribute will
   be set when the result object's ``status`` attribute is set to ``invalid recipient``
   and will contain more information about the failure in the form of ``reason:ident``
   where ``reason`` is a text description of the reason, and ``ident`` identifies the
   recipient key.

.. note:: Any public key provided for encryption should be trusted, otherwise
   encryption fails but without any warning. This is because gpg just prints a message
   to the console, but does not provide a specific error indication that the Python
   wrapper can use.

.. versionchanged:: 0.5.0
   The `stream` argument to :meth:`~gnupg.GPG.encrypt_file` can be a pathname
   to an existing file as well as text or a file-like object. In the pathname
   case, ``python-gnupg`` will open and close the file for you.

.. note::
   ``python-gnupg`` assumes that any object with a :attr:`read` attribute is a
   file-like object. Otherwise, if it corresponds to an existing file, then it is taken
   as a filename, and otherwise it must be the actual data to be processed.


.. index:: Decryption

Decryption
----------

To decrypt a message, use the :meth:`~gnupg.GPG.decrypt` method::

    >>> decrypted_data = gpg.decrypt(data)

If you want to decrypt data in a file (or file-like object), use
:meth:`~gnupg.GPG.decrypt_file` instead::

    >>> decrypted_data = gpg.decrypt_file(stream) # e.g. after stream = open(filename, 'rb')

These methods both return an object such that ``str(decrypted_data)`` gives the
decrypted data in a non-binary format. If decryption succeeded, the returned object's
``ok`` attribute is set to ``True`` and the ``data`` attribute holds the decrypted
data. Otherwise, the returned object's ``ok`` attribute is set to ``False`` and its
``status`` attribute (a message string) provides more information as to the reason for
failure (for example, ``'bad passphrase'`` or ``'decryption failed'``).

Both methods accept the following optional keyword arguments:

always_trust (defaults to ``False``)
    Skip key validation and assume that used keys are always fully trusted.
passphrase (defaults to ``None``)
    A passphrase to use when accessing the keyrings.
extra_args (defaults to ``None``)
    A list of additional arguments to pass to the ``gpg`` executable.
output (defaults to ``None``)
    The name of an output file to write to. If a name is specified, the decrypted
    output is written directly to the file.

.. versionadded:: 0.4.1
   The ``extra_args`` keyword argument was added.

.. versionadded:: 0.4.2
   Upon a successful decryption, the keyid of the decrypting key is stored in the
   ``key_id`` attribute of the result, if this information is provided by ``gpg``.

.. versionchanged:: 0.5.0
   The `stream` argument to :meth:`~gnupg.GPG.decrypt_file` can be a pathname
   to an existing file as well as text or a file-like object. In the pathname
   case, ``python-gnupg`` will open and close the file for you.

.. _caching-warning:

.. warning::
   **Passphrase caching:** By default, ``gpg-agent`` caches passphrases, and this can
   lead to unexpected results such as successfully decrypting messages even when
   passing the wrong passphrase. To avoid this, disable caching by putting the
   following two lines in ``gpg-agent.conf``:

   * ``default-cache-ttl 0`` and either
   * ``maximum-cache-ttl 0`` for GnuPG < 2.1, or
   * ``max-cache-ttl 0`` for GnuPG >= 2.1.

   For more information, see the `GnuPG documentation on agent configuration
   <https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html>`_.

Using signing and encryption together
-------------------------------------

If you want to use signing and encryption together, use the
:meth:`~gnupg.GPG.encrypt` with a signer fingerprint and the corresponding
passphrase::

    >>> encrypted_data = gpg.encrypt(data, recipients, sign=signer_fingerprint, passphrase=signer_passphrase)

The resulting encrypted data contains the signature. When decrypting the data, upon
successful decryption, signature verification is also performed (assuming the relevant
public keys are available at the recipient end). The results are stored in the object
returned from the :meth:`~gnupg.GPG.decrypt` call::

    >>> decrypted_data = gpg.decrypt(data, passphrase=recipient_passphrase)

At this point, if a signature is verified, signer information is held in attributes of
``decrypted_data``: ``username``, ``key_id``, ``signature_id``, ``fingerprint``,
``trust_level`` and ``trust_text``. If the message wasn't signed, these attributes
will all be set to ``None``.

The trust levels are (in increasing order) ``TRUST_UNDEFINED``, ``TRUST_NEVER``,
``TRUST_MARGINAL``, ``TRUST_FULLY`` and ``TRUST_ULTIMATE``. If verification succeeded,
you can test the trust level against known values as in the following example::

    decrypted_data = gpg.decrypt(data, passphrase=recipient_passphrase))
    if decrypted_data.trust_level is not None and decrypted_data.trust_level >= decrypted_data.TRUST_FULLY:
        print('Trust level: %s' % decrypted_data.trust_text)

.. versionadded:: 0.3.1
   The ``trust_level`` and ``trust_text`` attributes were added.


Finding the recipients for an encrypted message
-----------------------------------------------

Sometimes, it's desirable to find the recipients for an encrypted message,
without actually performing decryption. You can do this using the
:meth:`~gnupg.GPG.get_recipients` or
:meth:`~gnupg.GPG.get_recipients_file` methods:

    >>> ids = gpg.get_recipients(data)

or, with a file or file-like object:

    >>> ids = gpg.get_recipients_file(stream) # e.g. after stream = open(filename, 'rb')

.. versionadded:: 0.4.8
   The ``get_recipients`` and ``get_recipients_file`` methods were added.

.. versionchanged:: 0.5.0
   The `stream` argument to :meth:`~gnupg.GPG.get_recipients_file` can be a
   pathname to an existing file as well as text or a file-like object. In the
   pathname case, ``python-gnupg`` will open and close the file for you.


Custom handling of data streams
-------------------------------

During processing, ``gpg`` often sends output to its ``stdout`` stream, which is captured
by ``python-gnupg`` buffered, and returned as part of an operation's result (usually in
the ``data`` attribute). However, there might be times when you want to:

* Avoid buffering, as the data sizes involved are large.
* Process the data as it becomes available, before it's all available at the end of an
  operation. Most commonly, this will happen during decryption.

In such cases, you can supply a callable in the ``on_data`` attribute of a :class:`GPG`
instance before you invoke the operation. When an operation with ``gpg`` is initiated, if
``on_data`` is given a value, it will be called with each chunk of data (of type
``bytes``) received from ``gpg``, and its return value will be used to determine whether
``python-gnupg`` buffers the data. At the end of the data stream, it will be called with
a zero-length bytestring (allowing you do any necessary clean-up).

If the ``on_data`` callable returns ``False``, the data will *not* be buffered by
``python-gnupg``. For any other return value (including ``None``), the data *will* be
buffered. (This slightly odd arrangement is for backwards compatibility.)

Example usages (not tested, error handling omitted):

.. code-block:: python

    # Doing your own buffering in memory

    chunks = []

    def collector(chunk):
        chunks.append(chunk)
        return False  # Tell python-gnupg not to buffer the chunk

    gpg = GPG(...)
    gpg.on_data = collector
    gpg.decrypt(...)

    # Doing your own buffering in a file

    class Collector:
        def __init__(self, fn):
            self.out = open(fn, 'wb')

        def __call__(self, chunk):
            self.out.write(chunk)
            if not chunk:
                self.out.close()
            return False  # Tell python-gnupg not to buffer the chunk

    gpg = GPG(...)
    gpg.on_data = Collector('/tmp/plain.txt')
    gpg.decrypt(...)

    # Processing as you go (assuming the decrypted data is utf-8 encoded)

    import codecs

    class Processor:
        def __init__(self, fn):
            self.out = open(fn, 'w', encoding='utf-8')
            self.decoder =  codecs.getincrementaldecoder('utf-8')
            self.result = ''

        def __call__(self, chunk):
            final = (len(chunk) == 0)
            self.result += self.decoder.decode(chunk, final)
            # Perhaps do custom processing of self.result here
            self.out.write(self.result)
            self.result = ''
            if final:
                self.out.close()
            return False  # Tell python-gnupg not to buffer the chunk

    gpg = GPG(...)
    gpg.on_data = Processor('/tmp/plain.txt')
    gpg.decrypt(...)


Threading constraints on processing data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The `on_data` callable is called from a background thread which is reading data from the
`gpg` child process. Sometimes, there are constraints on where certain processing can be
done (e.g. code involving SQLAlchemy sessions or Qt GUI updates needs to be run on a
specific thread, not just any thread). To handle this, you can use a queue, as in this
example (not tested, error handling omitted):

.. code-block:: python

    import queue

    class ChunkForwarder:
        def __init__(self, queue):
            self.queue = queue

        def __call__(self, chunk):
            self.queue.put(chunk)
            return False  # Tell python-gnupg not to buffer the chunk

    # In the thread where you need to process chunks
    gpg = GPG(...)
    q = queue.Queue()
    gpg.on_data = ChunkForwarder(q)
    # call the operation you want to perform. Chunks will be sent to the queue.
    gpg.decrypt(...)
    while True:
        chunk = q.get()
        q.task_done()  # keep things tidy
        if not chunk:
            break
        # process the chunk using a SQLAlchemy session, Qt widget or whatever
        process_chunk(chunk)


Signing and Verification
========================

Data intended for digital signing is signed with the private key of the signer. Each
recipient can verify the signed data using the corresponding public key.

.. index:: Signing

Signing
-------

To sign a message, use the :meth:`~gnupg.GPG.sign` method::

    >>> signed_data = gpg.sign(message)

or, for data in a file (or file-like object), you can use the
:meth:`~gnupg.GPG.sign_file` method instead::

    >>> signed_data = gpg.sign_file(stream) # e.g. after stream = open(filename, "rb")

These methods both return an object such that ``str(signed_data)`` gives the signed
data in a non-binary format. They accept the following optional keyword arguments:

keyid (defaults to ``None``)
    The id for the key which will be used to do the signing. If not specified, the
    first key in the secret keyring is used.
passphrase (defaults to ``None``)
    A passphrase to use when accessing the keyrings.
clearsign (defaults to ``True``)
    Returns a clear text signature, i.e. one which can be read without any special
    software.
detach (defaults to ``False``)
    Returns a detached signature. If you specify ``True`` for this, then the detached
    signature will not be clear text, i.e. it will be as if you had specified a
    ``False`` value for *clearsign*. This is because if both are specified, gpg
    ignores the request for a detached signature.
binary (defaults to ``False``)
    If ``True``, a binary signature (rather than armored ASCII) is created.
output (defaults to ``None``)
    If specified, this is used as the file path where GPG outputs the signature.
    Convention dictates a ``.asc`` or ``.sig`` file extension for this.
extra_args (defaults to ``None``)
    A list of additional arguments to pass to the ``gpg`` executable.

Note: If the data being signed is binary, calling ``str(signed_data)`` may raise
exceptions. In that case, use the fact that ``signed_data.data`` holds the binary
signed data. Usually the signature itself is ASCII; it's the message itself which may
cause the exceptions to be raised. (Unless a detached signature is requested, the
result of signing is the message with the signature appended.)

The hash algorithm used when creating the signature can be found in the
``signed_data.hash_algo`` attribute.

.. versionadded:: 0.2.5
   The ``detach`` keyword argument was added in version 0.2.5.

.. versionadded:: 0.2.6
   The ``binary`` keyword argument was added in version 0.2.6.

.. versionadded:: 0.3.7
   The ``output`` keyword argument was added in version 0.3.7.

.. versionadded:: 0.4.1
   The ``extra_args`` keyword argument was added.

.. versionadded:: 0.4.2
   The keyid and username of the signing key are stored in the ``key_id`` and
   ``username`` attributes of the result, if this information is provided by ``gpg``
   (which should happen if you specify ``extra_args=['--verbose']``).

.. versionchanged:: 0.5.0
   The *stream* argument to :meth:`~gnupg.GPG.sign_file` can be a pathname to
   an existing file as well as text or a file-like object. In the pathname
   case, ``python-gnupg`` will open and close the file for you.

.. versionadded:: 0.5.1
   The ``status_detail`` attribute was added to the result object. This attribute will
   be set when the result object's ``status`` attribute is set to ``invalid signer``
   and will contain more information about the failure in the form of ``reason:ident``
   where ``reason`` is a text description of the reason, and ``ident`` identifies the
   signing key.

.. index:: Verification

Verification
------------

To verify some data which you've received, use the
:meth:`~gnupg.GPG.verify` method::

    >>> verified = gpg.verify(data)

To verify data in a file (or file-like object), use :meth:`~gnupg.GPG.verify_file`::

    >>> verified = gpg.verify_file(stream) # e.g. after stream = open(filename, "rb")

You can use the returned value in a Boolean context::

    >>> if not verified: raise ValueError("Signature could not be verified!")

Getting the signed data out while verifying
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If you clearsign data, the signature envelops the signed data (whether text or
binary) with the signature, but by default you won't get this data back from a
:meth:`~gnupg.GPG.verify` or :meth:`~gnupg.GPG.verify_file` call. In order to
extract the signed data, you need to pass more information to the ``verify``
methods about where you want that data (if none is specified, the data is
discarded). To write it to ``gpg``'s standard output, specify
``extra_args=['-o', '-']``. In that case, it will be returned as a bytestring
in ``verified.data``. Alternatively, to write to a file, you can pass
``extra_args=['-o', 'path/to/write/data.to']`` and it will be written to the
file you specify. (Thanks to Mark Neil for this suggestion.)


Verifying detached signatures on disk
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If you want to verify a detached signature, use :meth:`~gnupg.GPG.verify_file`::

    >>> verified = gpg.verify_file(stream, path_to_data_file)

Note that in this case, the ``stream`` contains the *signature* to be verified. The
data that was signed should be in a separate file whose path is indicated by
``path_to_data_file``.

.. versionadded:: 0.2.5
   The second argument to verify_file (``data_filename``) was added.

.. versionadded:: 0.4.1
   An optional keyword argument to verify_file (``close_file``) was added. This
   defaults to ``True``, but if set to ``False``, the signature stream is not closed.
   It's then left to the caller to close it when appropriate.

   An optional keyword argument ``extra_args`` was added. This defaults to ``None``,
   but if a value is specified, it should be a list of extra arguments to pass to the
   ``gpg`` executable.

.. versionadded:: 0.4.4
   When signature verification is performed, multiple signatures might be present.
   Information about all signatures is now captured in a ``sig_info`` attribute of the
   value returned from ``verify``. This is a dictionary keyed by the signature ID and
   whose values are dictionaries containing the following information (note - all are
   string values):

   * ``fingerprint`` - the fingerprint of the signing key. * ``pubkey_fingerprint`` -
     this is usually the same as ``fingerprint``, but it might be different if a
     subkey was used for the signing.

   * ``keyid`` - the key id.

   * ``username`` - user information for the signing key.

   * ``status`` - this indicates the status of the signature.

   * ``creation_date`` - the creation date of the signature in text format, YYYY-MM-DD.

   * ``timestamp`` - the signature creation time as a timestamp.

   * ``expiry`` - the signature expiry time as a timestamp, or ``'0'`` to
     indicate no expiry.

   * ``trust_level`` - the trust level, see below.

   * ``trust_text`` - the text corresponding to the trust level.

   Note that only information for valid signatures will be present in ``sig_info``.

When a signature is verified, signer information is held in attributes of
``verified``: ``username``, ``key_id``, ``signature_id``, ``fingerprint``,
``trust_level`` and ``trust_text``. If the message wasn't signed, these attributes
will all be set to ``None``. If there were multiple signatures, the last values seen
will be shown.

The trust levels are (in increasing order) TRUST_UNDEFINED, TRUST_NEVER,
TRUST_MARGINAL, TRUST_FULLY and TRUST_ULTIMATE. If verification succeeded, you can
test the trust level against known values as in the following example::

    verified = gpg.verify(data)
    if verified.trust_level is not None and verified.trust_level >= verified.TRUST_FULLY:
        print('Trust level: %s' % verified.trust_text)

.. versionadded:: 0.3.1
   The ``trust_level`` and ``trust_text`` attributes were added.

Note that even if you have a valid signature, you may want to not rely on that
validity, if the key used for signing has expired or was revoked. If this information
is available, it will be in the ``key_status`` attribute =, and the result will still
be ``False`` in a Boolean context. If there is no problem detected with the signing
key, the ``key_status`` attribute will be ``None``.

.. versionadded:: 0.3.3
   The ``key_status`` attribute was added.

.. versionadded:: 0.4.2
   The keyid and username of the signing key are stored in the ``key_id`` and
   ``username`` attributes of the result, if this information is provided by ``gpg``.

.. versionchanged:: 0.5.0
   The `stream` argument to :meth:`~gnupg.GPG.verify_file` can be a pathname to
   an existing file as well as text or a file-like object. In the pathname
   case, ``python-gnupg`` will open and close the file for you.

.. versionadded:: 0.5.1
   A ``problems`` attribute was added which holds problems reported by ``gpg``
   during verification. This is a list of dictionaries, one for each reported
   problem. Each dictionary will have ``status`` and ``keyid`` keys indicating
   the problem and the corresponding key; other information in the dictionaries
   will be error specific.


Verifying detached signatures in memory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You can also verify detached signatures where the data is in memory, using
:meth:`~gnupg.GPG.verify_data`::

    >>> verified = gpg.verify_data(path_to_signature_file, data)

where *data* should be a byte string of the data to be verified against the signature
in the file named by *path_to_signature_file*. The returned value is the same as for
the other verification methods.

In addition, an ``extra_args`` keyword parameter can be specified. If provided, this
is treated as a list of additional arguments to pass to the ``gpg`` executable.

.. versionadded:: 0.3.6
   The :meth:`~gnupg.GPG.verify_data` method was added.

.. versionadded:: 0.4.1
   The ``extra_args`` keyword argument was added.

Accessing gpg's Return Code
===========================

Starting with version 0.4.8, return values to all calls which implement ``gpg``
operations, other than :meth:`~gnupg.GPG.export_keys`, will have a ``returncode``
attribute which is the return code returned by the ``gpg`` invocation made to perform the
operation (the result of :meth:`~gnupg.GPG.export_keys` is the set of exported keys and
doesn't have this attribute).

.. versionadded:: 0.4.8
    The ``returncode`` attribute was added to result instances.

Passphrases
===========

Passphrases provided to ``python-gnupg`` are not stored persistently, and just passed
through to the ``GnuPG`` executable through a pipe. The user of ``python-gnupg``  is
responsible for taking care not to store passphrases where they may become available
to malicious code or malicious users, as well as the physical and security aspects of
managing their private keys.


.. index:: Logging

.. _logging:

Logging
=======

The module makes use of the facilities provided by Python's ``logging`` package. A
single logger is created with the module's ``__name__``, hence ``gnupg`` unless you
rename the module. A ``NullHandler`` instance is added to this logger, so if you don't
use logging in your application which uses this module, you shouldn't see any logging
messages. If you do use logging in your application, just configure it in the normal
way.

.. index:: Download

Test Harness
============

The distribution includes a test harness, ``test_gnupg.py``, which contains unit tests
covering the functionality described above. You can invoke ``test_gnupg.py`` with one
or more optional command-line arguments. If no arguments are provided, all tests are
run. If arguments are provided, they collectively determine which of the tests will be
run:

import
    Run tests relating to key import
crypt
    Run tests relating to encryption and decryption
sign
    Run tests relating to signing and verification
key
    Run tests relating to key management
basic
    Run basic tests relating to environment setup, or which don't fit into one of the
    above categories

Download
========

The latest version is available from the `PyPI
<https://pypi.python.org/pypi/python-gnupg>`_ page.

The source code repository can be found `here
<https://github.com/vsajip/python-gnupg/>`__.

Status and Further Work
=======================

The ``gnupg`` module, being based on proven earlier versions, is quite usable, and
comes packaged with Linux distributions such as Debian, Ubuntu and Fedora. However,
there may be some features of GnuPG which this module does not take advantage of, or
provide access to. How this module evolves will be determined by feedback from its
user community.

Support for GnuPG 2.1 is limited, because that version of GnuPG does not provide the
ability to prevent pinentry popups in all cases. This package sends passphrases to the
``gpg`` executable via pipes, which is only possible under GnuPG 2.1 under limited
conditions and requiring end-users to edit GnuPG configuration files.

At present, functionality that requires interacting with the ``gpg`` executable (e.g.
for key editing) is not available. This is because it requires essentially a state
machine which manages the interaction - moreover, a state machine which varies
according to the specific version of the ``gpg`` executable being used.

If you find bugs and want to raise issues, please do so via the `project issue tracker
<https://github.com/vsajip/python-gnupg/issues/new/choose>`_.

All feedback will be gratefully received; please send it to the `discussion group
<https://groups.google.com/group/python-gnupg>`_.

.. cssclass:: hidden

Index
=====

* :ref:`genindex`


================================================
FILE: docs/requirements.txt
================================================
sphinxcontrib-spelling==7.6.2
sphinx<7
sphinx-rtd-theme>=1.2.2


================================================
FILE: docs/spelling_wordlist.txt
================================================
Cunnane
Folkinshteyn
Dmitry
Gladkov
Abdul
Karim
Yann
Leboulanger
Kirill
Yakovenko
Leftwich
Michal
Niklas
Noël
Jannis
Leidel
Venzen
Khaosan
Pörner
Kévin
dprovins
ernest
eyepulp
hysterix
slackin
natureshadow
gpgbinary
gpg
env
Kuchling
Traugott
Wayback
gnupg
etc
latin
Autogenerated
Bloggs
Gamal
pinentry
keyid
keyids
Jython
algo
ownertrust
uid
sig
Keygrip
keygrip
truthy
libgpg
th
armor
clearsign
armored
args
popups


================================================
FILE: gnupg.py
================================================
""" A wrapper for the GnuPG `gpg` command.

Portions of this module are derived from A.M. Kuchling's well-designed
GPG.py, using Richard Jones' updated version 1.3, which can be found
in the pycrypto CVS repository on Sourceforge:

http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py

This module is *not* forward-compatible with amk's; some of the
old interface has changed.  For instance, since I've added decrypt
functionality, I elected to initialize with a 'gnupghome' argument
instead of 'keyring', so that gpg can find both the public and secret
keyrings.  I've also altered some of the returned objects in order for
the caller to not have to know as much about the internals of the
result classes.

While the rest of ISconf is released under the GPL, I am releasing
this single file under the same terms that A.M. Kuchling used for
pycrypto.

Steve Traugott, stevegt@terraluna.org
Thu Jun 23 21:27:20 PDT 2005

This version of the module has been modified from Steve Traugott's version
(see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by
Vinay Sajip to make use of the subprocess module (Steve's version uses os.fork()
and so does not work on Windows). Renamed to gnupg.py to avoid confusion with
the previous versions.

Modifications Copyright (C) 2008-2026 Vinay Sajip. All rights reserved.

For the full documentation, see https://docs.red-dove.com/python-gnupg/ or
https://gnupg.readthedocs.io/
"""

import codecs
from datetime import datetime
from email.utils import parseaddr
from io import StringIO
import logging
import os
try:
    from queue import Queue, Empty
except ImportError:
    from Queue import Queue, Empty
import re
import socket
from subprocess import Popen, PIPE
import sys
import threading

__version__ = '0.5.7.dev0'
__author__ = 'Vinay Sajip'
__date__ = '$31-Dec-2025 16:41:34$'

STARTUPINFO = None
if os.name == 'nt':  # pragma: no cover
    try:
        from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW, SW_HIDE
    except ImportError:
        STARTUPINFO = None

try:
    unicode
    _py3k = False
    string_types = basestring
    text_type = unicode
    path_types = (bytes, str)
except NameError:
    _py3k = True
    string_types = str
    text_type = str
    path_types = (str, )

logger = logging.getLogger(__name__)
if not logger.handlers:
    logger.addHandler(logging.NullHandler())

# See gh-196: Logging could show sensitive data. It also produces some voluminous
# output. Hence, split into two tiers - stuff that's always logged, and stuff that's
# only logged if log_everything is True. (This is set by the test script.)
#
# For now, only debug logging of chunks falls into the optionally-logged category.
log_everything = False

# We use the test below because it works for Jython as well as CPython
if os.path.__name__ == 'ntpath':  # pragma: no cover
    # On Windows, we don't need shell quoting, other than worrying about
    # paths with spaces in them.
    def shell_quote(s):
        return '"%s"' % s
else:
    # Section copied from sarge

    # This regex determines which shell input needs quoting
    # because it may be unsafe
    UNSAFE = re.compile(r'[^\w%+,./:=@-]')

    def shell_quote(s):
        """
        Quote text so that it is safe for POSIX command shells.

        For example, "*.py" would be converted to "'*.py'". If the text is considered safe it is returned unquoted.

        Args:
            s (str): The value to quote
        Returns:
            str: A safe version of the input, from the point of view of POSIX
                 command shells.
        """
        if not isinstance(s, string_types):  # pragma: no cover
            raise TypeError('Expected string type, got %s' % type(s))
        if not s:  # pragma: no cover
            result = "''"
        elif not UNSAFE.search(s):  # pragma: no cover
            result = s
        else:
            result = "'%s'" % s.replace("'", r"'\''")
        return result

    # end of sarge code

# Now that we use shell=False, we shouldn't need to quote arguments.
# Use no_quote instead of shell_quote to remind us of where quoting
# was needed. However, note that we still need, on 2.x, to encode any
# Unicode argument with the file system encoding - see Issue #41 and
# Python issue #1759845 ("subprocess.call fails with unicode strings in
# command line").

# Allows the encoding used to be overridden in special cases by setting
# this module attribute appropriately.
fsencoding = sys.getfilesystemencoding()


def no_quote(s):
    """
    Legacy function which is a no-op on Python 3.
    """
    if not _py3k and isinstance(s, text_type):
        s = s.encode(fsencoding)
    return s


def _copy_data(instream, outstream, buffer_size, error_queue):
    # Copy one stream to another
    assert buffer_size > 0
    sent = 0
    if hasattr(sys.stdin, 'encoding'):
        enc = sys.stdin.encoding
    else:  # pragma: no cover
        enc = 'ascii'
    while True:
        # See issue #39: read can fail when e.g. a text stream is provided
        # for what is actually a binary file
        try:
            data = instream.read(buffer_size)
        except Exception as e:  # pragma: no cover
            logger.warning('Exception occurred while reading', exc_info=1)
            error_queue.put_nowait(e)
            break
        if not data:
            break
        sent += len(data)
        # logger.debug('sending chunk (%d): %r', sent, data[:256])
        try:
            outstream.write(data)
        except UnicodeError:  # pragma: no cover
            outstream.write(data.encode(enc))
        except Exception as e:  # pragma: no cover
            # Can sometimes get 'broken pipe' errors even when the data has all
            # been sent
            logger.exception('Error sending data')
            error_queue.put_nowait(e)
            break
    try:
        outstream.close()
    except IOError:  # pragma: no cover
        logger.warning('Exception occurred while closing: ignored', exc_info=1)
    logger.debug('closed output, %d bytes sent', sent)


def _threaded_copy_data(instream, outstream, buffer_size, error_queue):
    assert buffer_size > 0
    wr = threading.Thread(target=_copy_data, args=(instream, outstream, buffer_size, error_queue))
    wr.daemon = True
    logger.debug('data copier: %r, %r, %r', wr, instream, outstream)
    wr.start()
    return wr


def _write_passphrase(stream, passphrase, encoding):
    passphrase = '%s\n' % passphrase
    passphrase = passphrase.encode(encoding)
    stream.write(passphrase)
    logger.debug('Wrote passphrase')


def _is_sequence(instance):
    return isinstance(instance, (list, tuple, set, frozenset))


def _make_memory_stream(s):
    try:
        from io import BytesIO
        rv = BytesIO(s)
    except ImportError:  # pragma: no cover
        rv = StringIO(s)
    return rv


def _make_binary_stream(s, encoding):
    if _py3k:
        if isinstance(s, str):
            s = s.encode(encoding)
    else:
        if type(s) is not str:
            s = s.encode(encoding)
    return _make_memory_stream(s)


class StatusHandler(object):
    """
    The base class for handling status messages from `gpg`.
    """

    on_data_failure = None  # set at instance level when failures occur

    def __init__(self, gpg):
        """
        Initialize an instance.

        Args:
            gpg (GPG): The :class:`GPG` instance in use.
        """
        self.gpg = gpg

    def handle_status(self, key, value):
        """
        Handle status messages from the `gpg` child process. These are lines of the format

            [GNUPG:] <key> <value>

        Args:
            key (str): Identifies what the status message is.
            value (str): Identifies additional data, which differs depending on the key.
        """
        raise NotImplementedError


class Verify(StatusHandler):
    """
    This class handles status messages during signature verificaton.
    """

    TRUST_EXPIRED = 0
    TRUST_UNDEFINED = 1
    TRUST_NEVER = 2
    TRUST_MARGINAL = 3
    TRUST_FULLY = 4
    TRUST_ULTIMATE = 5

    TRUST_LEVELS = {
        'TRUST_EXPIRED': TRUST_EXPIRED,
        'TRUST_UNDEFINED': TRUST_UNDEFINED,
        'TRUST_NEVER': TRUST_NEVER,
        'TRUST_MARGINAL': TRUST_MARGINAL,
        'TRUST_FULLY': TRUST_FULLY,
        'TRUST_ULTIMATE': TRUST_ULTIMATE,
    }

    # for now, just the most common error codes. This can be expanded as and
    # when reports come in of other errors.
    GPG_SYSTEM_ERROR_CODES = {
        1: 'permission denied',
        35: 'file exists',
        81: 'file not found',
        97: 'not a directory',
    }

    GPG_ERROR_CODES = {
        11: 'incorrect passphrase',
    }

    returncode = None

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.valid = False
        self.fingerprint = self.creation_date = self.timestamp = None
        self.signature_id = self.key_id = None
        self.username = None
        self.key_id = None
        self.key_status = None
        self.status = None
        self.pubkey_fingerprint = None
        self.expire_timestamp = None
        self.sig_timestamp = None
        self.trust_text = None
        self.trust_level = None
        self.sig_info = {}
        self.problems = []

    def __nonzero__(self):  # pragma: no cover
        return self.valid

    __bool__ = __nonzero__

    def handle_status(self, key, value):

        def update_sig_info(**kwargs):
            sig_id = self.signature_id
            if sig_id:
                info = self.sig_info[sig_id]
                info.update(kwargs)
            else:
                logger.debug('Ignored due to missing sig iD: %s', kwargs)

        if key in self.TRUST_LEVELS:
            self.trust_text = key
            self.trust_level = self.TRUST_LEVELS[key]
            update_sig_info(trust_level=self.trust_level, trust_text=self.trust_text)
            # See Issue #214. Once we see this, we're done with the signature just seen.
            # Zap the signature ID, because we don't see a SIG_ID unless we have a new
            # good signature.
            self.signature_id = None
        elif key in ('WARNING', 'ERROR'):  # pragma: no cover
            logger.warning('potential problem: %s: %s', key, value)
        elif key == 'BADSIG':  # pragma: no cover
            self.valid = False
            self.status = 'signature bad'
            self.key_id, self.username = value.split(None, 1)
            self.problems.append({'status': self.status, 'keyid': self.key_id, 'user': self.username})
            update_sig_info(keyid=self.key_id, username=self.username, status=self.status)
        elif key == 'ERRSIG':  # pragma: no cover
            self.valid = False
            parts = value.split()
            (self.key_id, algo, hash_algo, cls, self.timestamp) = parts[:5]
            # Since GnuPG 2.2.7, a fingerprint is tacked on
            if len(parts) >= 7:
                self.fingerprint = parts[6]
            self.status = 'signature error'
            update_sig_info(keyid=self.key_id,
                            timestamp=self.timestamp,
                            fingerprint=self.fingerprint,
                            status=self.status)
            self.problems.append({
                'status': self.status,
                'keyid': self.key_id,
                'timestamp': self.timestamp,
                'fingerprint': self.fingerprint
            })
        elif key == 'EXPSIG':  # pragma: no cover
            self.valid = False
            self.status = 'signature expired'
            self.key_id, self.username = value.split(None, 1)
            update_sig_info(keyid=self.key_id, username=self.username, status=self.status)
            self.problems.append({'status': self.status, 'keyid': self.key_id, 'user': self.username})
        elif key == 'GOODSIG':
            self.valid = True
            self.status = 'signature good'
            self.key_id, self.username = value.split(None, 1)
            update_sig_info(keyid=self.key_id, username=self.username, status=self.status)
        elif key == 'VALIDSIG':
            parts = value.split()
            fingerprint, creation_date, sig_ts, expire_ts = parts[:4]
            (self.fingerprint, self.creation_date, self.sig_timestamp,
             self.expire_timestamp) = (fingerprint, creation_date, sig_ts, expire_ts)
            # may be different if signature is made with a subkey
            if len(parts) >= 10:
                self.pubkey_fingerprint = parts[9]
            self.status = 'signature valid'
            update_sig_info(fingerprint=fingerprint,
                            creation_date=creation_date,
                            timestamp=sig_ts,
                            expiry=expire_ts,
                            pubkey_fingerprint=self.pubkey_fingerprint,
                            status=self.status)
        elif key == 'SIG_ID':
            sig_id, creation_date, timestamp = value.split()
            self.sig_info[sig_id] = {'creation_date': creation_date, 'timestamp': timestamp}
            (self.signature_id, self.creation_date, self.timestamp) = (sig_id, creation_date, timestamp)
        elif key == 'NO_PUBKEY':  # pragma: no cover
            self.valid = False
            self.key_id = value
            self.status = 'no public key'
            self.problems.append({'status': self.status, 'keyid': self.key_id})
        elif key == 'NO_SECKEY':  # pragma: no cover
            self.valid = False
            self.key_id = value
            self.status = 'no secret key'
            self.problems.append({'status': self.status, 'keyid': self.key_id})
        elif key in ('EXPKEYSIG', 'REVKEYSIG'):  # pragma: no cover
            # signed with expired or revoked key
            self.valid = False
            self.key_id, self.username = value.split(None, 1)
            if key == 'EXPKEYSIG':
                self.key_status = 'signing key has expired'
            else:
                self.key_status = 'signing key was revoked'
            self.status = self.key_status
            update_sig_info(status=self.status, keyid=self.key_id)
            self.problems.append({'status': self.status, 'keyid': self.key_id})
        elif key in ('UNEXPECTED', 'FAILURE'):  # pragma: no cover
            self.valid = False
            if key == 'UNEXPECTED':
                self.status = 'unexpected data'
            else:
                # N.B. there might be other reasons. For example, if an output
                # file can't  be created - /dev/null/foo will lead to a
                # "not a directory" error, but which is not sent as a status
                # message with the [GNUPG:] prefix. Similarly if you try to
                # write to "/etc/foo" as a non-root user, a "permission denied"
                # error will be sent as a non-status message.
                message = 'error - %s' % value
                operation, code = value.rsplit(' ', 1)
                if code.isdigit():
                    code = int(code) & 0xFFFFFF  # lose the error source
                    if self.gpg.error_map and code in self.gpg.error_map:
                        message = '%s: %s' % (operation, self.gpg.error_map[code])
                    else:
                        system_error = bool(code & 0x8000)
                        code = code & 0x7FFF
                        if system_error:
                            mapping = self.GPG_SYSTEM_ERROR_CODES
                        else:
                            mapping = self.GPG_ERROR_CODES
                        if code in mapping:
                            message = '%s: %s' % (operation, mapping[code])
                if not self.status:
                    self.status = message
        elif key == 'NODATA':  # pragma: no cover
            # See issue GH-191
            self.valid = False
            self.status = 'signature expected but not found'
        elif key in ('DECRYPTION_INFO', 'PLAINTEXT', 'PLAINTEXT_LENGTH', 'BEGIN_SIGNING', 'KEY_CONSIDERED'):
            pass
        elif key in ('NEWSIG', ):
            # Only sent in gpg2. Clear any signature ID, to be set by a following SIG_ID
            self.signature_id = None
        else:  # pragma: no cover
            logger.debug('message ignored: %r, %r', key, value)


class ImportResult(StatusHandler):
    """
    This class handles status messages during key import.
    """

    counts = '''count no_user_id imported imported_rsa unchanged n_uids n_subk n_sigs n_revoc sec_read sec_imported
            sec_dups not_imported'''.split()

    returncode = None

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.results = []
        self.fingerprints = []
        for result in self.counts:
            setattr(self, result, 0)

    def __nonzero__(self):
        return bool(not self.not_imported and self.fingerprints)

    __bool__ = __nonzero__

    ok_reason = {
        '0': 'Not actually changed',
        '1': 'Entirely new key',
        '2': 'New user IDs',
        '4': 'New signatures',
        '8': 'New subkeys',
        '16': 'Contains private key',
    }

    problem_reason = {
        '0': 'No specific reason given',
        '1': 'Invalid Certificate',
        '2': 'Issuer Certificate missing',
        '3': 'Certificate Chain too long',
        '4': 'Error storing certificate',
    }

    def handle_status(self, key, value):
        if key in ('WARNING', 'ERROR'):  # pragma: no cover
            logger.warning('potential problem: %s: %s', key, value)
        elif key in ('IMPORTED', 'KEY_CONSIDERED'):
            # this duplicates info we already see in import_ok & import_problem
            pass
        elif key == 'NODATA':  # pragma: no cover
            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'No valid data found'})
        elif key == 'IMPORT_OK':
            reason, fingerprint = value.split()
            reasons = []
            for code, text in list(self.ok_reason.items()):
                if int(reason) | int(code) == int(reason):
                    reasons.append(text)
            reasontext = '\n'.join(reasons) + '\n'
            self.results.append({'fingerprint': fingerprint, 'ok': reason, 'text': reasontext})
            self.fingerprints.append(fingerprint)
        elif key == 'IMPORT_PROBLEM':  # pragma: no cover
            try:
                reason, fingerprint = value.split()
            except Exception:
                reason = value
                fingerprint = '<unknown>'
            self.results.append({'fingerprint': fingerprint, 'problem': reason, 'text': self.problem_reason[reason]})
        elif key == 'IMPORT_RES':
            import_res = value.split()
            for i, count in enumerate(self.counts):
                setattr(self, count, int(import_res[i]))
        elif key == 'KEYEXPIRED':  # pragma: no cover
            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Key expired'})
        elif key == 'SIGEXPIRED':  # pragma: no cover
            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Signature expired'})
        elif key == 'FAILURE':  # pragma: no cover
            self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Other failure'})
        else:  # pragma: no cover
            logger.debug('message ignored: %s, %s', key, value)

    def summary(self):
        """
        Return a summary indicating how many keys were imported and how many were not imported.
        """
        result = []
        result.append('%d imported' % self.imported)
        if self.not_imported:  # pragma: no cover
            result.append('%d not imported' % self.not_imported)
        return ', '.join(result)


ESCAPE_PATTERN = re.compile(r'\\x([0-9a-f][0-9a-f])', re.I)
BASIC_ESCAPES = {
    r'\n': '\n',
    r'\r': '\r',
    r'\f': '\f',
    r'\v': '\v',
    r'\b': '\b',
    r'\0': '\0',
}


class SendResult(StatusHandler):
    """
    This class handles status messages during key sending.
    """

    returncode = None

    def handle_status(self, key, value):
        logger.debug('SendResult: %s: %s', key, value)


def _set_fields(target, fieldnames, args):
    for i, var in enumerate(fieldnames):
        if i < len(args):
            target[var] = args[i]
        else:
            target[var] = 'unavailable'


class SearchKeys(StatusHandler, list):
    """
    This class handles status messages during key search.
    """

    # Handle pub and uid (relating the latter to the former).
    # Don't care about the rest

    UID_INDEX = 1
    FIELDS = 'type keyid algo length date expires'.split()
    returncode = None

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.curkey = None
        self.fingerprints = []
        self.uids = []
        self.uid_map = {}

    def get_fields(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        result = {}
        _set_fields(result, self.FIELDS, args)
        result['uids'] = []
        result['sigs'] = []
        return result

    def pub(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        self.curkey = curkey = self.get_fields(args)
        self.append(curkey)

    def uid(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        uid = args[self.UID_INDEX]
        uid = ESCAPE_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), uid)
        for k, v in BASIC_ESCAPES.items():
            uid = uid.replace(k, v)
        self.curkey['uids'].append(uid)
        self.uids.append(uid)
        uid_data = {}
        self.uid_map[uid] = uid_data
        for fn, fv in zip(self.FIELDS, args):
            uid_data[fn] = fv

    def handle_status(self, key, value):  # pragma: no cover
        pass


class ListKeys(SearchKeys):
    """
    This class handles status messages during listing keys and signatures.

    Handle pub and uid (relating the latter to the former).

    We don't care about (info from GnuPG DETAILS file):

    crt = X.509 certificate
    crs = X.509 certificate and private key available
    uat = user attribute (same as user id except for field 10).
    sig = signature
    rev = revocation signature
    pkd = public key data (special field format, see below)
    grp = reserved for gpgsm
    rvk = revocation key
    """

    UID_INDEX = 9
    FIELDS = ('type trust length algo keyid date expires dummy ownertrust uid sig'
              ' cap issuer flag token hash curve compliance updated origin keygrip').split()

    def __init__(self, gpg):
        super(ListKeys, self).__init__(gpg)
        self.in_subkey = False
        self.key_map = {}

    def key(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        self.curkey = curkey = self.get_fields(args)
        if curkey['uid']:  # pragma: no cover
            curkey['uids'].append(curkey['uid'])
        del curkey['uid']
        curkey['subkeys'] = []
        self.append(curkey)
        self.in_subkey = False

    pub = sec = key

    def fpr(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        fp = args[9]
        if fp in self.key_map and self.gpg.check_fingerprint_collisions:  # pragma: no cover
            raise ValueError('Unexpected fingerprint collision: %s' % fp)
        if not self.in_subkey:
            self.curkey['fingerprint'] = fp
            self.fingerprints.append(fp)
            self.key_map[fp] = self.curkey
        else:
            self.curkey['subkeys'][-1][2] = fp
            self.key_map[fp] = self.curkey

    def grp(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        grp = args[9]
        if not self.in_subkey:
            self.curkey['keygrip'] = grp
        else:
            self.curkey['subkeys'][-1][3] = grp

    def _collect_subkey_info(self, curkey, args):
        info_map = curkey.setdefault('subkey_info', {})
        info = {}
        _set_fields(info, self.FIELDS, args)
        info_map[args[4]] = info

    def sub(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        # See issue #81. We create a dict with more information about
        # subkeys, but for backward compatibility reason, have to add it in
        # as a separate entry 'subkey_info'
        subkey = [args[4], args[11], None, None]  # keyid, type, fp, grp
        self.curkey['subkeys'].append(subkey)
        self._collect_subkey_info(self.curkey, args)
        self.in_subkey = True

    def ssb(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        subkey = [args[4], None, None, None]  # keyid, type, fp, grp
        self.curkey['subkeys'].append(subkey)
        self._collect_subkey_info(self.curkey, args)
        self.in_subkey = True

    def sig(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        # keyid, uid, sigclass
        self.curkey['sigs'].append((args[4], args[9], args[10]))


class ScanKeys(ListKeys):
    """
    This class handles status messages during scanning keys.
    """

    def sub(self, args):
        """
        Internal method used to update the instance from a `gpg` status message.
        """
        # --with-fingerprint --with-colons somehow outputs fewer colons,
        # use the last value args[-1] instead of args[11]
        subkey = [args[4], args[-1], None, None]
        self.curkey['subkeys'].append(subkey)
        self._collect_subkey_info(self.curkey, args)
        self.in_subkey = True


class TextHandler(object):

    def _as_text(self):
        return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)

    if _py3k:
        __str__ = _as_text
    else:
        __unicode__ = _as_text

        def __str__(self):
            return self.data


_INVALID_KEY_REASONS = {
    0: 'no specific reason given',
    1: 'not found',
    2: 'ambiguous specification',
    3: 'wrong key usage',
    4: 'key revoked',
    5: 'key expired',
    6: 'no crl known',
    7: 'crl too old',
    8: 'policy mismatch',
    9: 'not a secret key',
    10: 'key not trusted',
    11: 'missing certificate',
    12: 'missing issuer certificate',
    13: 'key disabled',
    14: 'syntax error in specification',
}


def _determine_invalid_recipient_or_signer(s):  # pragma: no cover
    parts = s.split()
    if len(parts) >= 2:
        code, ident = parts[:2]
    else:
        code = parts[0]
        ident = '<no ident>'
    unexpected = 'unexpected return code %r' % code
    try:
        key = int(code)
        result = _INVALID_KEY_REASONS.get(key, unexpected)
    except ValueError:
        result = unexpected
    return '%s:%s' % (result, ident)


class Crypt(Verify, TextHandler):
    """
    This class handles status messages during encryption and decryption.
    """

    def __init__(self, gpg):
        Verify.__init__(self, gpg)
        self.data = ''
        self.ok = False
        self.status = ''
        self.status_detail = ''
        self.key_id = None

    def __nonzero__(self):
        return bool(self.ok)

    __bool__ = __nonzero__

    def handle_status(self, key, value):
        if key in ('WARNING', 'ERROR'):
            logger.warning('potential problem: %s: %s', key, value)
        elif key == 'NODATA':
            if self.status not in ('decryption failed', ):
                self.status = 'no data was provided'
        elif key in ('NEED_PASSPHRASE', 'BAD_PASSPHRASE', 'GOOD_PASSPHRASE', 'MISSING_PASSPHRASE', 'KEY_NOT_CREATED',
                     'NEED_PASSPHRASE_PIN'):  # pragma: no cover
            self.status = key.replace('_', ' ').lower()
        elif key == 'DECRYPTION_FAILED':  # pragma: no cover
            if self.status != 'no secret key':  # don't overwrite more useful message
                self.status = 'decryption failed'
        elif key == 'NEED_PASSPHRASE_SYM':
            self.status = 'need symmetric passphrase'
        elif key == 'BEGIN_DECRYPTION':
            if self.status != 'no secret key':  # don't overwrite more useful message
                self.status = 'decryption incomplete'
        elif key == 'BEGIN_ENCRYPTION':
            self.status = 'encryption incomplete'
        elif key == 'DECRYPTION_OKAY':
            self.status = 'decryption ok'
            self.ok = True
        elif key == 'END_ENCRYPTION':
            self.status = 'encryption ok'
            self.ok = True
        elif key == 'INV_RECP':  # pragma: no cover
            if not self.status:
                self.status = 'invalid recipient'
            else:
                self.status = 'invalid recipient: %s' % self.status
            self.status_detail = _determine_invalid_recipient_or_signer(value)
        elif key == 'KEYEXPIRED':  # pragma: no cover
            self.status = 'key expired'
        elif key == 'SIG_CREATED':  # pragma: no cover
            self.status = 'sig created'
        elif key == 'SIGEXPIRED':  # pragma: no cover
            self.status = 'sig expired'
        elif key == 'ENC_TO':  # pragma: no cover
            # ENC_TO <long_keyid> <keytype> <keylength>
            self.key_id = value.split(' ', 1)[0]
        elif key in ('USERID_HINT', 'GOODMDC', 'END_DECRYPTION', 'CARDCTRL', 'BADMDC', 'SC_OP_FAILURE',
                     'SC_OP_SUCCESS', 'PINENTRY_LAUNCHED'):
            pass
        else:
            Verify.handle_status(self, key, value)


class GenKey(StatusHandler):
    """
    This class handles status messages during key generation.
    """

    returncode = None

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.type = None
        self.fingerprint = ''
        self.status = None

    def __nonzero__(self):  # pragma: no cover
        return bool(self.fingerprint)

    __bool__ = __nonzero__

    def __str__(self):  # pragma: no cover
        return self.fingerprint

    def handle_status(self, key, value):
        if key in ('WARNING', 'ERROR'):  # pragma: no cover
            logger.warning('potential problem: %s: %s', key, value)
        elif key == 'KEY_CREATED':
            parts = value.split()
            (self.type, self.fingerprint) = parts[:2]
            self.status = 'ok'
        elif key == 'KEY_NOT_CREATED':
            self.status = key.replace('_', ' ').lower()
        elif key in ('PROGRESS', 'GOOD_PASSPHRASE'):  # pragma: no cover
            pass
        else:  # pragma: no cover
            logger.debug('message ignored: %s, %s', key, value)


class AddSubkey(StatusHandler):
    """
    This class handles status messages during subkey addition.
    """

    returncode = None

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.type = None
        self.fingerprint = ''
        self.status = None

    def __nonzero__(self):  # pragma: no cover
        return bool(self.fingerprint)

    __bool__ = __nonzero__

    def __str__(self):
        return self.fingerprint

    def handle_status(self, key, value):
        if key in ('WARNING', 'ERROR'):  # pragma: no cover
            logger.warning('potential problem: %s: %s', key, value)
        elif key == 'KEY_CREATED':
            (self.type, self.fingerprint) = value.split()
            self.status = 'ok'
        else:  # pragma: no cover
            logger.debug('message ignored: %s, %s', key, value)


class ExportResult(GenKey):
    """
    This class handles status messages during key export.
    """

    # For now, just use an existing class to base it on - if needed, we
    # can override handle_status for more specific message handling.

    def handle_status(self, key, value):
        if key in ('EXPORTED', 'EXPORT_RES'):
            pass
        else:
            super(ExportResult, self).handle_status(key, value)


class DeleteResult(StatusHandler):
    """
    This class handles status messages during key deletion.
    """

    returncode = None

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.status = 'ok'

    def __str__(self):
        return self.status

    problem_reason = {
        '1': 'No such key',
        '2': 'Must delete secret key first',
        '3': 'Ambiguous specification',
    }

    def handle_status(self, key, value):
        if key == 'DELETE_PROBLEM':  # pragma: no cover
            self.status = self.problem_reason.get(value, 'Unknown error: %r' % value)
        else:  # pragma: no cover
            logger.debug('message ignored: %s, %s', key, value)

    def __nonzero__(self):  # pragma: no cover
        return self.status == 'ok'

    __bool__ = __nonzero__


class TrustResult(DeleteResult):
    """
    This class handles status messages during key trust setting.
    """
    pass


class Sign(StatusHandler, TextHandler):
    """
    This class handles status messages during signing.
    """

    returncode = None

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.type = None
        self.hash_algo = None
        self.fingerprint = None
        self.status = None
        self.status_detail = None
        self.key_id = None
        self.username = None

    def __nonzero__(self):
        return self.fingerprint is not None

    __bool__ = __nonzero__

    def handle_status(self, key, value):
        if key in ('WARNING', 'ERROR', 'FAILURE'):  # pragma: no cover
            logger.warning('potential problem: %s: %s', key, value)
        elif key in ('KEYEXPIRED', 'SIGEXPIRED'):  # pragma: no cover
            self.status = 'key expired'
        elif key == 'KEYREVOKED':  # pragma: no cover
            self.status = 'key revoked'
        elif key == 'SIG_CREATED':
            (self.type, algo, self.hash_algo, cls, self.timestamp, self.fingerprint) = value.split()
            self.status = 'signature created'
        elif key == 'USERID_HINT':  # pragma: no cover
            self.key_id, self.username = value.split(' ', 1)
        elif key == 'BAD_PASSPHRASE':  # pragma: no cover
            self.status = 'bad passphrase'
        elif key in ('INV_SGNR', 'INV_RECP'):  # pragma: no cover
            # INV_RECP is returned in older versions
            if not self.status:
                self.status = 'invalid signer'
            else:
                self.status = 'invalid signer: %s' % self.status
            self.status_detail = _determine_invalid_recipient_or_signer(value)
        elif key in ('NEED_PASSPHRASE', 'GOOD_PASSPHRASE', 'BEGIN_SIGNING'):
            pass
        else:  # pragma: no cover
            logger.debug('message ignored: %s, %s', key, value)


class AutoLocateKey(StatusHandler):
    """
    This class handles status messages during key auto-locating.
    fingerprint: str
    key_length: int
    created_at: date
    email: str
    email_real_name: str
    """

    def __init__(self, gpg):
        StatusHandler.__init__(self, gpg)
        self.fingerprint = None
        self.type = None
        self.created_at = None
        self.email = None
        self.email_real_name = None

    def handle_status(self, key, value):
        if key == "IMPORTED":
            _, email, display_name = value.split()

            self.email = email
            self.email_real_name = display_name[1:-1]
        elif key == "KEY_CONSIDERED":
            self.fingerprint = value.strip().split()[0]

    def pub(self, args):
        """
        Internal method to handle the 'pub' status message.
        `pub` message contains the fingerprint of the public key, its type and its creation date.
        """
        pass

    def uid(self, args):
        self.created_at = datetime.fromtimestamp(int(args[5]))
        raw_email_content = args[9]
        email, real_name = parseaddr(raw_email_content)
        self.email = email
        self.email_real_name = real_name

    def sub(self, args):
        self.key_length = int(args[2])

    def fpr(self, args):
        # Only store the first fingerprint
        self.fingerprint = self.fingerprint or args[9]


VERSION_RE = re.compile(r'\bcfg:version:(\d+(\.\d+)*)'.encode('ascii'))
HEX_DIGITS_RE = re.compile(r'[0-9a-f]+$', re.I)
PUBLIC_KEY_RE = re.compile(r'gpg: public key is (\w+)')


class GPG(object):
    """
    This class provides a high-level programmatic interface for `gpg`.
    """
    error_map = None

    decode_errors = 'strict'

    buffer_size = 16384  # override in instance if needed

    result_map = {
        'crypt': Crypt,
        'delete': DeleteResult,
        'generate': GenKey,
        'addSubkey': AddSubkey,
        'import': ImportResult,
        'send': SendResult,
        'list': ListKeys,
        'scan': ScanKeys,
        'search': SearchKeys,
        'sign': Sign,
        'trust': TrustResult,
        'verify': Verify,
        'export': ExportResult,
        'auto-locate-key': AutoLocateKey,
    }
    "A map of GPG operations to result object types."

    def __init__(self,
                 gpgbinary='gpg',
                 gnupghome=None,
                 verbose=False,
                 use_agent=False,
                 keyring=None,
                 options=None,
                 secret_keyring=None,
                 env=None):
        """Initialize a GPG process wrapper.

        Args:
            gpgbinary (str): A pathname for the GPG binary to use.

            gnupghome (str): A pathname to where we can find the public and private keyrings. The default is
                             whatever `gpg` defaults to.

            keyring (str|list): The name of alternative keyring file to use, or a list of such keyring files. If
                                specified, the default keyring is not used.

            options (list): A list of additional options to pass to the GPG binary.

            secret_keyring (str|list): The name of an alternative secret keyring file to use, or a list of such
                                       keyring files.

            env (dict): A dict of environment variables to be used for the GPG subprocess.
        """
        self.gpgbinary = gpgbinary
        self.gnupghome = gnupghome
        self.env = env
        # issue 112: fail if the specified value isn't a directory
        if gnupghome and not os.path.isdir(gnupghome):
            raise ValueError('gnupghome should be a directory (it isn\'t): %s' % gnupghome)
        if keyring:
            # Allow passing a string or another iterable. Make it uniformly
            # a list of keyring filenames
            if isinstance(keyring, string_types):
                keyring = [keyring]
        self.keyring = keyring
        if secret_keyring:  # pragma: no cover
            # Allow passing a string or another iterable. Make it uniformly
            # a list of keyring filenames
            if isinstance(secret_keyring, string_types):
                secret_keyring = [secret_keyring]
        self.secret_keyring = secret_keyring
        self.verbose = verbose
        self.use_agent = use_agent
        if isinstance(options, str):  # pragma: no cover
            options = [options]
        self.options = options
        self.on_data = None  # or a callable - will be called with data chunks
        # Changed in 0.3.7 to use Latin-1 encoding rather than
        # locale.getpreferredencoding falling back to sys.stdin.encoding
        # falling back to utf-8, because gpg itself uses latin-1 as the default
        # encoding.
        self.encoding = 'latin-1'
        if gnupghome and not os.path.isdir(self.gnupghome):  # pragma: no cover
            os.makedirs(self.gnupghome, 0o700)
        try:
            p = self._open_subprocess(['--list-config', '--with-colons'])
        except OSError:
            msg = 'Unable to run gpg (%s) - it may not be available.' % self.gpgbinary
            logger.exception(msg)
            raise OSError(msg)
        result = self.result_map['verify'](self)  # any result will do for this
        self._collect_output(p, result, stdin=p.stdin)
        if p.returncode != 0:  # pragma: no cover
            raise ValueError('Error invoking gpg: %s: %s' % (p.returncode, result.stderr))
        m = VERSION_RE.search(result.data)
        if not m:  # pragma: no cover
            self.version = None
        else:
            dot = '.'.encode('ascii')
            self.version = tuple([int(s) for s in m.groups()[0].split(dot)])

        # See issue #97. It seems gpg allow duplicate keys in keyrings, so we
        # can't be too strict.
        self.check_fingerprint_collisions = False

    def make_args(self, args, passphrase):
        """
        Make a list of command line elements for GPG. The value of ``args``
        will be appended. The ``passphrase`` argument needs to be True if
        a passphrase will be sent to `gpg`, else False.

        Args:
            args (list[str]): A list of arguments.
            passphrase (str): The passphrase to use.
        """
        cmd = [self.gpgbinary, '--status-fd', '2', '--no-tty', '--no-verbose']
        if 'DEBUG_IPC' in os.environ:  # pragma: no cover
            cmd.extend(['--debug', 'ipc'])
        if passphrase and hasattr(self, 'version'):
            if self.version >= (2, 1):
                cmd[1:1] = ['--pinentry-mode', 'loopback']
        cmd.extend(['--fixed-list-mode', '--batch', '--with-colons'])
        if self.gnupghome:
            cmd.extend(['--homedir', no_quote(self.gnupghome)])
        if self.keyring:
            cmd.append('--no-default-keyring')
            for fn in self.keyring:
                cmd.extend(['--keyring', no_quote(fn)])
        if self.secret_keyring:  # pragma: no cover
            for fn in self.secret_keyring:
                cmd.extend(['--secret-keyring', no_quote(fn)])
        if passphrase:
            cmd.extend(['--passphrase-fd', '0'])
        if self.use_agent:  # pragma: no cover
            cmd.append('--use-agent')
        if self.options:
            cmd.extend(self.options)
        cmd.extend(args)
        return cmd

    def _open_subprocess(self, args, passphrase=False):
        # Internal method: open a pipe to a GPG subprocess and return
        # the file objects for communicating with it.

        from subprocess import list2cmdline as debug_print

        cmd = self.make_args(args, passphrase)
        if self.verbose:  # pragma: no cover
            print(debug_print(cmd))
        if not STARTUPINFO:
            si = None
        else:  # pragma: no cover
            si = STARTUPINFO()
            si.dwFlags = STARTF_USESHOWWINDOW
            si.wShowWindow = SW_HIDE
        result = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE, startupinfo=si, env=self.env)
        logger.debug('%s: %s', result.pid, debug_print(cmd))
        return result

    def _read_response(self, stream, result):
        # Internal method: reads all the stderr output from GPG, taking notice
        # only of lines that begin with the magic [GNUPG:] prefix.
        #
        # Calls methods on the response object for each valid token found,
        # with the arg being the remainder of the status line.
        lines = []
        while True:
            line = stream.readline()
            if len(line) == 0:
                break
            lines.append(line)
            line = line.rstrip()
            if self.verbose:  # pragma: no cover
                print(line)
            logger.debug('%s', line)
            if line[0:9] == '[GNUPG:] ':
                # Chop off the prefix
                line = line[9:]
                L = line.split(None, 1)
                keyword = L[0]
                if len(L) > 1:
                    value = L[1]
                else:
                    value = ''
                result.handle_status(keyword, value)
        result.stderr = ''.join(lines)

    def _read_data(self, stream, result, on_data=None, buffer_size=1024):
        # Read the contents of the file from GPG's stdout
        assert buffer_size > 0
        chunks = []
        on_data_failure = None
        while True:
            data = stream.read(buffer_size)
            if len(data) == 0:
                if on_data:
                    try:
                        on_data(data)
                    except Exception as e:
                        if on_data_failure is None:
                            on_data_failure = e
                break
            if log_everything:
                logger.debug('chunk: %r' % data[:256])
            append = True
            if on_data:
                try:
                    on_data_result = on_data(data)
                    append = on_data_result is not False
                except Exception as e:
                    if on_data_failure is None:
                        on_data_failure = e
            if append:
                chunks.append(data)
        if _py3k:
            # Join using b'' or '', as appropriate
            result.data = type(data)().join(chunks)
        else:
            result.data = ''.join(chunks)
        if on_data_failure:
            result.on_data_failure = on_data_failure

    def _collect_output(self, process, result, writer=None, stdin=None):
        """
        Drain the subprocesses output streams, writing the collected output to the result. If a writer thread (writing
        to the subprocess) is given, make sure it's joined before returning. If a stdin stream is given, close it
        before returning.
        """
        stderr = codecs.getreader(self.encoding)(process.stderr)
        rr = threading.Thread(target=self._read_response, args=(stderr, result))
        rr.daemon = True
        logger.debug('stderr reader: %r', rr)
        rr.start()

        stdout = process.stdout
        dr = threading.Thread(target=self._read_data, args=(stdout, result, self.on_data, self.buffer_size))
        dr.daemon = True
        logger.debug('stdout reader: %r', dr)
        dr.start()

        dr.join()
        rr.join()
        if writer is not None:
            writer.join(0.01)
        process.wait()
        result.returncode = rc = process.returncode
        if rc != 0:
            logger.warning('gpg returned a non-zero error code: %d', rc)
        if stdin is not None:
            try:
                stdin.close()
            except IOError:  # pragma: no cover
                pass
        stderr.close()
        stdout.close()
        return rc

    def is_valid_file(self, fileobj):
        """
        A simplistic check for a file-like object.

        Args:
            fileobj (object): The object to test.
        Returns:
            bool: ``True`` if it's a file-like object, else ``False``.
        """
        return hasattr(fileobj, 'read')

    def _get_fileobj(self, fileobj_or_path):
        if self.is_valid_file(fileobj_or_path):
            result = fileobj_or_path
        elif not isinstance(fileobj_or_path, path_types):
            raise TypeError('Not a valid file or path: %s' % fileobj_or_path)
        elif not os.path.exists(fileobj_or_path):
            raise ValueError('No such file: %s' % fileobj_or_path)
        else:
            result = open(fileobj_or_path, 'rb')
        return result

    def _handle_io(self, args, fileobj_or_path, result, passphrase=None, binary=False):
        "Handle a call to GPG - pass input data, collect output data"
        # Handle a basic data call - pass data to GPG, handle the output
        # including status information. Garbage In, Garbage Out :)
        fileobj = self._get_fileobj(fileobj_or_path)
        writer = None  # See issue #237
        try:
            p = self._open_subprocess(args, passphrase is not None)
            if not binary:  # pragma: no cover
                stdin = codecs.getwriter(self.encoding)(p.stdin)
            else:
                stdin = p.stdin
            if passphrase:
                _write_passphrase(stdin, passphrase, self.encoding)
            error_queue = Queue()
            writer = _threaded_copy_data(fileobj, stdin, self.buffer_size, error_queue)
            self._collect_output(p, result, writer, stdin)
            try:
                exc = error_queue.get_nowait()
                # if we get here, that means an error occurred in the copying thread
                raise exc
            except Empty:
                pass
            return result
        finally:
            if writer:
                writer.join(0.01)
            if fileobj is not fileobj_or_path:
                fileobj.close()

    #
    # SIGNATURE METHODS
    #

    def sign(self, message, **kwargs):
        """
        Sign a message. This method delegates most of the work to the `sign_file()` method.

        Args:
            message (str|bytes): The data to sign.
            kwargs (dict): Keyword arguments, which are passed to `sign_file()`:

                * keyid (str): The key id of the signer.

                * passphrase (str): The passphrase for the key.

                * clearsign (bool): Whether to use clear signing.

                * detach (bool): Whether to produce a detached signature.

                * binary (bool): Whether to produce a binary signature.

                * output (str): The path to write a detached signature to.

                * extra_args (list[str]): Additional arguments to pass to `gpg`.
        """
        f = _make_binary_stream(message, self.encoding)
        result = self.sign_file(f, **kwargs)
        f.close()
        return result

    def set_output_without_confirmation(self, args, output):
        """
        If writing to a file which exists, avoid a confirmation message by
        updating the *args* value in place to set the output path and avoid
        any cpmfirmation prompt.

        Args:
            args (list[str]): A list of arguments.
            output (str): The path to the outpur file.
        """
        if os.path.exists(output):
            # We need to avoid an overwrite confirmation message
            args.extend(['--yes'])
        args.extend(['--output', no_quote(output)])

    def is_valid_passphrase(self, passphrase):
        """
        Confirm that the passphrase doesn't contain newline-type characters - it is passed in a pipe to `gpg`,
        and so not checking could lead to spoofing attacks by passing arbitrary text after passphrase and newline.

        Args:
            passphrase (str): The passphrase to test.

        Returns:
            bool: ``True`` if it's a valid passphrase, else ``False``.
        """
        return ('\n' not in passphrase and '\r' not in passphrase and '\x00' not in passphrase)

    def sign_file(self,
                  fileobj_or_path,
                  keyid=None,
                  passphrase=None,
                  clearsign=True,
                  detach=False,
                  binary=False,
                  output=None,
                  extra_args=None):
        """
        Sign data in a file or file-like object.

        Args:
            fileobj_or_path (str|file): The file or file-like object to sign.

            keyid (str): The key id of the signer.

            passphrase (str): The passphrase for the key.

            clearsign (bool): Whether to use clear signing.

            detach (bool): Whether to produce a detached signature.

            binary (bool): Whether to produce a binary signature.

            output (str): The path to write a detached signature to.

            extra_args (list[str]): Additional arguments to pass to `gpg`.
        """
        if passphrase and not self.is_valid_passphrase(passphrase):
            raise ValueError('Invalid passphrase')
        logger.debug('sign_file: %s', fileobj_or_path)
        if binary:  # pragma: no cover
            args = ['-s']
        else:
            args = ['-sa']
        # You can't specify detach-sign and clearsign together: gpg ignores
        # the detach-sign in that case.
        if detach:
            args.append('--detach-sign')
        elif clearsign:
            args.append('--clearsign')
        if keyid:
            args.extend(['--default-key', no_quote(keyid)])
        if output:  # pragma: no cover
            # write the output to a file with the specified name
            self.set_output_without_confirmation(args, output)

        if extra_args:  # pragma: no cover
            args.extend(extra_args)
        result = self.result_map['sign'](self)
        # We could use _handle_io here except for the fact that if the
        # passphrase is bad, gpg bails and you can't write the message.
        fileobj = self._get_fileobj(fileobj_or_path)
        p = self._open_subprocess(args, passphrase is not None)
        writer = None
        try:
            stdin = p.stdin
            if passphrase:
                _write_passphrase(stdin, passphrase, self.encoding)
            error_queue = Queue()
            writer = _threaded_copy_data(fileobj, stdin, self.buffer_size, error_queue)
            try:
                exc = error_queue.get_nowait()
                # if we get here, that means an error occurred in the copying thread
                raise exc
            except Empty:
                pass
        except IOError:  # pragma: no cover
            logging.exception('error writing message')
        finally:
            if writer:
                writer.join(0.01)
            if fileobj is not fileobj_or_path:
                fileobj.close()
        self._collect_output(p, result, writer, stdin)
        return result

    def verify(self, data, **kwargs):
        """
        Verify the signature on the contents of the string *data*. This method delegates most of the work to
        `verify_file()`.

        Args:
            data (str|bytes): The data to verify.
            kwargs (dict): Keyword arguments, which are passed to `verify_file()`:

                * fileobj_or_path (str|file): A path to a signature, or a file-like object containing one.

                * data_filename (str): If the signature is a detached one, the path to the data that was signed.

                * close_file (bool): If a file-like object is passed in, whether to close it.

                * extra_args (list[str]): Additional arguments to pass to `gpg`.
        """
        f = _make_binary_stream(data, self.encoding)
        result = self.verify_file(f, **kwargs)
        f.close()
        return result

    def verify_file(self, fileobj_or_path, data_filename=None, close_file=True, extra_args=None):
        """
        Verify a signature.

        Args:
            fileobj_or_path (str|file): A path to a signature, or a file-like object containing one.

            data_filename (str): If the signature is a detached one, the path to the data that was signed.

            close_file (bool): If a file-like object is passed in, whether to close it.

            extra_args (list[str]): Additional arguments to pass to `gpg`.
        """
        logger.debug('verify_file: %r, %r', fileobj_or_path, data_filename)
        result = self.result_map['verify'](self)
        args = ['--verify']
        if extra_args:  # pragma: no cover
            args.extend(extra_args)
        if data_filename is None:
            self._handle_io(args, fileobj_or_path, result, binary=True)
        else:
            logger.debug('Handling detached verification')
            import tempfile
            fileobj = self._get_fileobj(fileobj_or_path)
            fd, fn = tempfile.mkstemp(prefix='pygpg-')
            s = fileobj.read()
            if fileobj is not fileobj_or_path:
                fileobj.close()
            elif close_file:
                fileobj_or_path.close()
            logger.debug('Wrote to temp file: %r', s)
            os.write(fd, s)
            os.close(fd)
            args.append(no_quote(fn))
            args.append(no_quote(data_filename))
            try:
                p = self._open_subprocess(args)
                self._collect_output(p, result, stdin=p.stdin)
            finally:
                os.remove(fn)
        return result

    def verify_data(self, sig_filename, data, extra_args=None):
        """
        Verify the signature in sig_filename against data in memory

        Args:
            sig_filename (str): The path to a signature.

            data (str|bytes): The data to be verified.

            extra_args (list[str]): Additional arguments to pass to `gpg`.
        """
        logger.debug('verify_data: %r, %r ...', sig_filename, data[:16])
        result = self.result_map['verify'](self)
        args = ['--verify']
        if extra_args:  # pragma: no cover
            args.extend(extra_args)
        args.extend([no_quote(sig_filename), '-'])
        stream = _make_memory_stream(data)
        self._handle_io(args, stream, result, binary=True)
        return result

    #
    # KEY MANAGEMENT
    #

    def import_keys(self, key_data, extra_args=None, passphrase=None):
        """
        Import the key_data into our keyring.

        Args:
            key_data (str|bytes): The key data to import.

            passphrase (str): The passphrase to use.

            extra_args (list[str]): Additional arguments to pass to `gpg`.
        """
        result = self.result_map['import'](self)
        logger.debug('import_keys: %r', key_data[:256])
        data = _make_binary_stream(key_data, self.encoding)
        args = ['--import']
        if extra_args:  # pragma: no cover
            args.extend(extra_args)
        self._handle_io(args, data, result, passphrase=passphrase, binary=True)
        logger.debug('import_keys result: %r', result.__dict__)
        data.close()
        return result

    def import_keys_file(self, key_path, **kwargs):
        """
        Import the key data in key_path into our keyring.

        Args:
            key_path (str): A path to the key data to be imported.
        """
        with open(key_path, 'rb') as f:
            return self.import_keys(f.read(), **kwargs)

    def recv_keys(self, keyserver, *keyids, **kwargs):
        """
        Import one or more keys from a keyserver.

        Args:
            keyserver (str): The key server hostname.

            keyids (str): A list of key ids to receive.
        """
        result = self.result_map['import'](self)
        logger.debug('recv_keys: %r', keyids)
        data = _make_binary_stream('', self.encoding)
        args = ['--keyserver', no_quote(keyserver)]
        if 'extra_args' in kwargs:  # pragma: no cover
            args.extend(kwargs['extra_args'])
        args.append('--recv-keys')
        args.extend([no_quote(k) for k in keyids])
        self._handle_io(args, data, result, binary=True)
        logger.debug('recv_keys result: %r', result.__dict__)
        data.close()
        return result

    # This function isn't exercised by tests, to avoid polluting external
    # key servers with test keys
    def send_keys(self, keyserver, *keyids, **kwargs):  # pragma: no cover
        """
        Send one or more keys to a keyserver.

        Args:
            keyserver (str): The key server hostname.

            keyids (list[str]): A list of key ids to send.
        """

        # Note: it's not practical to test this function without sending
        # arbitrary data to live keyservers.

        result = self.result_map['send'](self)
        logger.debug('send_keys: %r', keyids)
        data = _make_binary_stream('', self.encoding)
        args = ['--keyserver', no_quote(keyserver)]
        if 'extra_args' in kwargs:
            args.extend(kwargs['extra_args'])
        args.append('--send-keys')
        args.extend([no_quote(k) for k in keyids])
        self._handle_io(args, data, result, binary=True)
        logger.debug('send_keys result: %r', result.__dict__)
        data.close()
        return result

    def delete_keys(self, fingerprints, secret=False, passphrase=None, expect_passphrase=True, exclamation_mode=False):
        """
        Delete the indicated keys.

        Args:
            fingerprints (str|list[str]): The keys to delete.

            secret (bool): Whether to delete secret keys.

            passphrase (str): The passphrase to use.

            expect_passphrase (bool): Whether a passphrase is expected.

            exclamation_mode (bool): If specified, a `'!'` is appended to each fingerprint. This deletes only a subkey
                                     or an entire key, depending on what the fingerprint refers to.

        .. note:: Passphrases

           Since GnuPG 2.1, you can't delete secret keys without providing a passphrase. However, if you're expecting
           the passphrase to go to `gpg` via pinentry, you should specify expect_passphrase=False. (It's only checked
           for GnuPG >= 2.1).
        """
        if passphrase and not self.is_valid_passphrase(passphrase):  # pragma: no cover
            raise ValueError('Invalid passphrase')
        which = 'key'
        if secret:  # pragma: no cover
            if self.version >= (2, 1) and passphrase is None and expect_passphrase:
                raise ValueError('For GnuPG >= 2.1, deleting secret keys '
                                 'needs a passphrase to be provided')
            which = 'secret-key'
        if _is_sequence(fingerprints):  # pragma: no cover
            fingerprints = [no_quote(s) for s in fingerprints]
        else:
            fingerprints = [no_quote(fingerprints)]

        if exclamation_mode:
            fingerprints = [f + '!' for f in fingerprints]

        args = ['--delete-%s' % which]
        if secret and self.version >= (2, 1):
            args.insert(0, '--yes')
        args.extend(fingerprints)
        result = self.result_map['delete'](self)
        if not secret or self.version < (2, 1):
            p = self._open_subprocess(args)
            self._collect_output(p, result, stdin=p.stdin)
        else:
            # Need to send in a passphrase.
            f = _make_binary_stream('', self.encoding)
            try:
                self._handle_io(args, f, result, passphrase=passphrase, binary=True)
            finally:
                f.close()
        return result

    def export_keys(self,
                    keyids,
                    secret=False,
                    armor=True,
                    minimal=False,
                    passphrase=None,
                    expect_passphrase=True,
                    output=None):
        """
        Export the indicated keys. A 'keyid' is anything `gpg` accepts.

        Args:
            keyids (str|list[str]): A single keyid or a list of them.

            secret (bool): Whether to export secret keys.

            armor (bool): Whether to ASCII-armor the output.

            minimal (bool): Whether to pass `--export-options export-minimal` to `gpg`.

            passphrase (str): The passphrase to use.

            expect_passphrase (bool): Whether a passphrase is expected.

            output (str): If specified, the path to write the exported key(s) to.

        .. note:: Passphrases

           Since GnuPG 2.1, you can't export secret keys without providing a passphrase. However, if you're expecting
           the passphrase to go to `gpg` via pinentry, you should specify expect_passphrase=False. (It's only checked
           for GnuPG >= 2.1).
        """
        if passphrase and not self.is_valid_passphrase(passphrase):  # pragma: no cover
            raise ValueError('Invalid passphrase')
        which = ''
        if secret:
            which = '-secret-key'
            if self.version >= (2, 1) and passphrase is None and expect_passphrase:  # pragma: no cover
                raise ValueError('For GnuPG >= 2.1, exporting secret keys '
                                 'needs a passphrase to be provided')
        if _is_sequence(keyids):
            keyids = [no_quote(k) for k in keyids]
        else:
            keyids = [no_quote(keyids)]
        args = ['--export%s' % which]
        if armor:
            args.append('--armor')
        if minimal:  # pragma: no cover
            args.extend(['--export-options', 'export-minimal'])
        if output:  # pragma: no cover
            # write the output to a file with the specified name
            self.set_output_without_confirmation(args, output)
        args.extend(keyids)
        # gpg --export produces no status-fd output; stdout will be
        # empty in case of failure
        result = self.result_map['export'](self)
        if not secret or self.version < (2, 1):
            p = self._open_subprocess(args)
            self._collect_output(p, result, stdin=p.stdin)
        else:
            # Need to send in a passphrase.
            f = _make_binary_stream('', self.encoding)
            try:
                self._handle_io(args, f, result, passphrase=passphrase, binary=True)
            finally:
                f.close()
        logger.debug('export_keys result[:100]: %r', result.data[:100])
        # Issue #49: Return bytes if armor not specified, else text
        result = result.data
        if armor:
            result = result.decode(self.encoding, self.decode_errors)
        return result

    def _decode_result(self, result):
        lines = result.data.decode(self.encoding, self.decode_errors).splitlines()
        valid_keywords = 'pub uid sec fpr sub ssb sig grp'.split()
        for line in lines:
            if self.verbose:  # pragma: no cover
                print(line)
            logger.debug('line: %r', line.rstrip())
            if not line:  # pragma: no cover
                break
            L = line.strip().split(':')
            if not L:  # pragma: no cover
                continue
            keyword = L[0]
            if keyword in valid_keywords:
                getattr(result, keyword)(L)
        return result

    def _get_list_output(self, p, kind):
        # Get the response information
        result = self.result_map[kind](self)
        self._collect_output(p, result, stdin=p.stdin)
        return self._decode_result(result)

    def list_keys(self, secret=False, keys=None, sigs=False):
        """
        List the keys currently in the keyring.

        Args:
            secret (bool): Whether to list secret keys.

            keys (str|list[str]): A list of key ids to match.

            sigs (bool): Whether to include signature information.

        Returns:
            list[dict]: A list of dictionaries with key information.
        """

        if secret:
            which = 'secret-keys'
        else:
            which = 'sigs' if sigs else 'keys'
        args = ['--list-%s' % which, '--fingerprint', '--fingerprint']  # get subkey FPs, too

        if self.version >= (2, 1):
            args.append('--with-keygrip')

        if keys:
            if isinstance(keys, string_types):
                keys = [keys]
            args.extend(keys)
        p = self._open_subprocess(args)
        result = self._get_list_output(p, 'list')
        # Fix up subkey_info with fingerprint and grip values
        for key in result:
            # import pdb; pdb.set_trace()
            subkeys = key['subkeys']
            subkey_info = key.get('subkey_info')
            if subkey_info:
                for sk in subkeys:
                    skid, capability, fp, grp = sk
                    d = subkey_info[skid]
                    d['capability'] = capability
                    d['fingerprint'] = fp
                    d['keygrip'] = grp
        return result

    def scan_keys(self, filename):
        """
        List details of an ascii armored or binary key file without first importing it to the local keyring.

        Args:
            filename (str): The path to the file containing the key(s).

        .. warning:: Warning:
            Care is needed. The function works on modern GnuPG by running:

                $ gpg --dry-run --import-options import-show --import filename

            On older versions, it does the *much* riskier:

                $ gpg --with-fingerprint --with-colons filename
        """
        if self.version >= (2, 1):
            args = ['--dry-run', '--import-options', 'import-show', '--import']
        else:
            logger.warning('Trying to list packets, but if the file is not a '
                           'keyring, might accidentally decrypt')
            args = ['--with-fingerprint', '--with-colons', '--fixed-list-mode']
        args.append(no_quote(filename))
        p = self._open_subprocess(args)
        return self._get_list_output(p, 'scan')

    def scan_keys_mem(self, key_data):
        """
        List details of an ascii armored or binary key without first importing it to the local keyring.

        Args:
            key_data (str|bytes): The key data to import.

        .. warning:: Warning:
            Care is needed. The function works on modern GnuPG by running:

                $ gpg --dry-run --import-options import-show --import filename

            On older versions, it does the *much* riskier:

                $ gpg --with-fingerprint --with-colons filename
        """
        result = self.result_map['scan'](self)
        logger.debug('scan_keys: %r', key_data[:256])
        data = _make_binary_stream(key_data, self.encoding)
        if self.version >= (2, 1):
            args = ['--dry-run', '--import-options', 'import-show', '--import']
        else:
            logger.warning('Trying to list packets, but if the file is not a '
                           'keyring, might accidentally decrypt')
            args = ['--with-fingerprint', '--with-colons', '--fixed-list-mode']
        self._handle_io(args, data, result, binary=True)
        logger.debug('scan_keys result: %r', result.__dict__)
        data.close()
        return self._decode_result(result)

    def search_keys(self, query, keyserver='pgp.mit.edu', extra_args=None):
        """
        search a keyserver by query (using the `--search-keys` option).

        Args:
            query(str): The query to use.

            keyserver (str): The key server hostname.

            extra_args (list[str]): Additional arguments to pass to `gpg`.
        """
        query = query.strip()
        if HEX_DIGITS_RE.match(query):
            query = '0x' + query
        args = ['--fingerprint', '--keyserver', no_quote(keyserver)]
        if extra_args:  # pragma: no cover
            args.extend(extra_args)
        args.extend(['--search-keys', no_quote(query)])
        p = self._open_subprocess(args)

        # Get the response information
        result = self.result_map['search'](self)
        self._collect_output(p, result, stdin=p.stdin)
        lines = result.data.decode(self.encoding, self.decode_errors).splitlines()
        valid_keywords = ['pub', 'uid']
        for line in lines:
            if self.verbose:  # pragma: no cover
                print(line)
            logger.debug('line: %r', line.rstrip())
            if not line:  # sometimes get blank lines on Windows
                continue
            L = line.strip().split(':')
            if not L:  # pragma: no cover
                continue
            keyword = L[0]
            if keyword in valid_keywords:
                getattr(result, keyword)(L)
        return result

    def auto_locate_key(self, email, mechanisms=None, **kwargs):
        """
        Auto locate a public key by `email`.

        Args:
            email (str): The email address to search for.
            mechanisms (list[str]): A list of mechanisms to use. Valid mechanisms can be found
            here https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
            under "--auto-key-locate". Default: ['wkd', 'ntds', 'ldap', 'cert', 'dane', 'local']
        """
        mechanisms = mechanisms or ['wkd', 'ntds', 'ldap', 'cert', 'dane', 'local']

        args = ['--auto-key-locate', ','.join(mechanisms), '--locate-keys', email]

        result = self.result_map['auto-locate-key'](self)

        if 'extra_args' in kwargs:
            args.extend(kwargs['extra_args'])

        process = self._open_subprocess(args)
        self._collect_output(process, result, stdin=process.stdin)
        self._decode_result(result)
        return result

    def gen_key(self, input):
        """
        Generate a key; you might use `gen_key_input()` to create the input.

        Args:
            input (str): The input to the key creation operation.
        """
        args = ['--gen-key']
        result = self.result_map['generate'](self)
        f = _make_binary_stream(input, self.encoding)
        self._handle_io(args, f, result, binary=True)
        f.close()
        return result

    def gen_key_input(self, **kwargs):
        """
        Generate `--gen-key` input  (see `gpg` documentation in DETAILS).

        Args:
            kwargs (dict): A list of keyword arguments.
        Returns:
            str: A string suitable for passing to the `gen_key()` method.
        """

        parms = {}
        no_protection = kwargs.pop('no_protection', False)
        for key, val in list(kwargs.items()):
            key = key.replace('_', '-').title()
            if str(val).strip():  # skip empty strings
                parms[key] = val
        parms.setdefault('Key-Type', 'RSA')
        if 'key_curve' not in kwargs:
            parms.setdefault('Key-Length', 2048)
        parms.setdefault('Name-Real', 'Autogenerated Key')
        logname = (os.environ.get('LOGNAME') or os.environ.get('USERNAME') or 'unspecified')
        hostname = socket.gethostname()
        parms.setdefault('Name-Email', '%s@%s' % (logname.replace(' ', '_'), hostname))
        out = 'Key-Type: %s\n' % parms.pop('Key-Type')
        for key, val in list(parms.items()):
            out += '%s: %s\n' % (key, val)
        if no_protection:  # pragma: no cover
            out += '%no-protection\n'
        out += '%commit\n'
        return out

        # Key-Type: RSA
        # Key-Length: 1024
        # Name-Real: ISdlink Server on %s
        # Name-Comment: Created by %s
        # Name-Email: isdlink@%s
        # Expire-Date: 0
        # %commit
        #
        #
        # Key-Type: DSA
        # Key-Length: 1024
        # Subkey-Type: ELG-E
        # Subkey-Length: 1024
        # Name-Real: Joe Tester
        # Name-Comment: with stupid passphrase
        # Name-Email: joe@foo.bar
        # Expire-Date: 0
        # Passphrase: abc
        # %pubring foo.pub
        # %secring foo.sec
        # %commit

    def add_subkey(self, master_key, master_passphrase=None, algorithm='rsa', usage='encrypt', expire='-'):
        """
        Add subkeys to a master key,

        Args:
            master_key (str): The master key.

            master_passphrase (str): The passphrase for the master key.

            algorithm (str): The key algorithm to use.

            usage (str): The desired uses for the subkey.

            expire (str): The expiration date of the subkey.
        """
        if self.version[0] < 2:
            raise NotImplementedError('Not available in GnuPG 1.x')
        if not master_key:  # pragma: no cover
            raise ValueError('No master key fingerprint specified')

        if master_passphrase and not self.is_valid_passphrase(master_passphrase):
Download .txt
gitextract_4ty4owtm/

├── .coveragerc
├── .flake8
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       └── python-package.yml
├── .gitignore
├── .hgignore
├── .hgtags
├── .readthedocs.yaml
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── _static/
│   │   └── sidebar.js
│   ├── _templates/
│   │   └── page.html
│   ├── conf.py
│   ├── index.rst
│   ├── requirements.txt
│   └── spelling_wordlist.txt
├── gnupg.py
├── messages.json
├── package.json
├── pyproject.toml
├── release
├── setup.cfg
├── test_gnupg.py
├── test_pubring.gpg
├── test_secring.gpg
└── tox.ini
Download .txt
SYMBOL INDEX (187 symbols across 4 files)

FILE: docs/_static/sidebar.js
  function sidebar_is_collapsed (line 45) | function sidebar_is_collapsed() {
  function toggle_sidebar (line 49) | function toggle_sidebar() {
  function collapse_sidebar (line 56) | function collapse_sidebar() {
  function expand_sidebar (line 70) | function expand_sidebar() {
  function add_sidebar_button (line 86) | function add_sidebar_button() {
  function set_position_from_cookie (line 135) | function set_position_from_cookie() {

FILE: docs/conf.py
  function skip_module_docstring (line 287) | def skip_module_docstring(app, what, name, obj, options, lines):
  function setup (line 292) | def setup(app):

FILE: gnupg.py
  function shell_quote (line 90) | def shell_quote(s):
  function shell_quote (line 99) | def shell_quote(s):
  function no_quote (line 135) | def no_quote(s):
  function _copy_data (line 144) | def _copy_data(instream, outstream, buffer_size, error_queue):
  function _threaded_copy_data (line 182) | def _threaded_copy_data(instream, outstream, buffer_size, error_queue):
  function _write_passphrase (line 191) | def _write_passphrase(stream, passphrase, encoding):
  function _is_sequence (line 198) | def _is_sequence(instance):
  function _make_memory_stream (line 202) | def _make_memory_stream(s):
  function _make_binary_stream (line 211) | def _make_binary_stream(s, encoding):
  class StatusHandler (line 221) | class StatusHandler(object):
    method __init__ (line 228) | def __init__(self, gpg):
    method handle_status (line 237) | def handle_status(self, key, value):
  class Verify (line 250) | class Verify(StatusHandler):
    method __init__ (line 286) | def __init__(self, gpg):
    method __nonzero__ (line 303) | def __nonzero__(self):  # pragma: no cover
    method handle_status (line 308) | def handle_status(self, key, value):
  class ImportResult (line 444) | class ImportResult(StatusHandler):
    method __init__ (line 454) | def __init__(self, gpg):
    method __nonzero__ (line 461) | def __nonzero__(self):
    method handle_status (line 483) | def handle_status(self, key, value):
    method summary (line 520) | def summary(self):
  class SendResult (line 542) | class SendResult(StatusHandler):
    method handle_status (line 549) | def handle_status(self, key, value):
  function _set_fields (line 553) | def _set_fields(target, fieldnames, args):
  class SearchKeys (line 561) | class SearchKeys(StatusHandler, list):
    method __init__ (line 573) | def __init__(self, gpg):
    method get_fields (line 580) | def get_fields(self, args):
    method pub (line 590) | def pub(self, args):
    method uid (line 597) | def uid(self, args):
    method handle_status (line 612) | def handle_status(self, key, value):  # pragma: no cover
  class ListKeys (line 616) | class ListKeys(SearchKeys):
    method __init__ (line 638) | def __init__(self, gpg):
    method key (line 643) | def key(self, args):
    method fpr (line 657) | def fpr(self, args):
    method grp (line 672) | def grp(self, args):
    method _collect_subkey_info (line 682) | def _collect_subkey_info(self, curkey, args):
    method sub (line 688) | def sub(self, args):
    method ssb (line 700) | def ssb(self, args):
    method sig (line 709) | def sig(self, args):
  class ScanKeys (line 717) | class ScanKeys(ListKeys):
    method sub (line 722) | def sub(self, args):
  class TextHandler (line 734) | class TextHandler(object):
    method _as_text (line 736) | def _as_text(self):
    method __str__ (line 744) | def __str__(self):
  function _determine_invalid_recipient_or_signer (line 767) | def _determine_invalid_recipient_or_signer(s):  # pragma: no cover
  class Crypt (line 783) | class Crypt(Verify, TextHandler):
    method __init__ (line 788) | def __init__(self, gpg):
    method __nonzero__ (line 796) | def __nonzero__(self):
    method handle_status (line 801) | def handle_status(self, key, value):
  class GenKey (line 848) | class GenKey(StatusHandler):
    method __init__ (line 855) | def __init__(self, gpg):
    method __nonzero__ (line 861) | def __nonzero__(self):  # pragma: no cover
    method __str__ (line 866) | def __str__(self):  # pragma: no cover
    method handle_status (line 869) | def handle_status(self, key, value):
  class AddSubkey (line 884) | class AddSubkey(StatusHandler):
    method __init__ (line 891) | def __init__(self, gpg):
    method __nonzero__ (line 897) | def __nonzero__(self):  # pragma: no cover
    method __str__ (line 902) | def __str__(self):
    method handle_status (line 905) | def handle_status(self, key, value):
  class ExportResult (line 915) | class ExportResult(GenKey):
    method handle_status (line 923) | def handle_status(self, key, value):
  class DeleteResult (line 930) | class DeleteResult(StatusHandler):
    method __init__ (line 937) | def __init__(self, gpg):
    method __str__ (line 941) | def __str__(self):
    method handle_status (line 950) | def handle_status(self, key, value):
    method __nonzero__ (line 956) | def __nonzero__(self):  # pragma: no cover
  class TrustResult (line 962) | class TrustResult(DeleteResult):
  class Sign (line 969) | class Sign(StatusHandler, TextHandler):
    method __init__ (line 976) | def __init__(self, gpg):
    method __nonzero__ (line 986) | def __nonzero__(self):
    method handle_status (line 991) | def handle_status(self, key, value):
  class AutoLocateKey (line 1018) | class AutoLocateKey(StatusHandler):
    method __init__ (line 1028) | def __init__(self, gpg):
    method handle_status (line 1036) | def handle_status(self, key, value):
    method pub (line 1045) | def pub(self, args):
    method uid (line 1052) | def uid(self, args):
    method sub (line 1059) | def sub(self, args):
    method fpr (line 1062) | def fpr(self, args):
  class GPG (line 1072) | class GPG(object):
    method __init__ (line 1100) | def __init__(self,
    method make_args (line 1179) | def make_args(self, args, passphrase):
    method _open_subprocess (line 1214) | def _open_subprocess(self, args, passphrase=False):
    method _read_response (line 1233) | def _read_response(self, stream, result):
    method _read_data (line 1261) | def _read_data(self, stream, result, on_data=None, buffer_size=1024):
    method _collect_output (line 1296) | def _collect_output(self, process, result, writer=None, stdin=None):
    method is_valid_file (line 1331) | def is_valid_file(self, fileobj):
    method _get_fileobj (line 1342) | def _get_fileobj(self, fileobj_or_path):
    method _handle_io (line 1353) | def _handle_io(self, args, fileobj_or_path, result, passphrase=None, b...
    method sign (line 1387) | def sign(self, message, **kwargs):
    method set_output_without_confirmation (line 1414) | def set_output_without_confirmation(self, args, output):
    method is_valid_passphrase (line 1429) | def is_valid_passphrase(self, passphrase):
    method sign_file (line 1442) | def sign_file(self,
    method verify (line 1520) | def verify(self, data, **kwargs):
    method verify_file (line 1542) | def verify_file(self, fileobj_or_path, data_filename=None, close_file=...
    method verify_data (line 1584) | def verify_data(self, sig_filename, data, extra_args=None):
    method import_keys (line 1609) | def import_keys(self, key_data, extra_args=None, passphrase=None):
    method import_keys_file (line 1631) | def import_keys_file(self, key_path, **kwargs):
    method recv_keys (line 1641) | def recv_keys(self, keyserver, *keyids, **kwargs):
    method send_keys (line 1665) | def send_keys(self, keyserver, *keyids, **kwargs):  # pragma: no cover
    method delete_keys (line 1691) | def delete_keys(self, fingerprints, secret=False, passphrase=None, exp...
    method export_keys (line 1746) | def export_keys(self,
    method _decode_result (line 1819) | def _decode_result(self, result):
    method _get_list_output (line 1836) | def _get_list_output(self, p, kind):
    method list_keys (line 1842) | def list_keys(self, secret=False, keys=None, sigs=False):
    method scan_keys (line 1886) | def scan_keys(self, filename):
    method scan_keys_mem (line 1912) | def scan_keys_mem(self, key_data):
    method search_keys (line 1942) | def search_keys(self, query, keyserver='pgp.mit.edu', extra_args=None):
    method auto_locate_key (line 1981) | def auto_locate_key(self, email, mechanisms=None, **kwargs):
    method gen_key (line 2005) | def gen_key(self, input):
    method gen_key_input (line 2019) | def gen_key_input(self, **kwargs):
    method add_subkey (line 2072) | def add_subkey(self, master_key, master_passphrase=None, algorithm='rs...
    method quick_sign_key (line 2103) | def quick_sign_key(self, certifier_fingerprint, recipient_fingerprint,...
    method encrypt_file (line 2135) | def encrypt_file(self,
    method encrypt (line 2213) | def encrypt(self, data, recipients, **kwargs):
    method decrypt (line 2243) | def decrypt(self, message, **kwargs):
    method decrypt_file (line 2266) | def decrypt_file(self, fileobj_or_path, always_trust=False, passphrase...
    method get_recipients (line 2296) | def get_recipients(self, message, **kwargs):
    method get_recipients_file (line 2312) | def get_recipients_file(self, fileobj_or_path, extra_args=None):
    method trust_keys (line 2331) | def trust_keys(self, fingerprints, trustlevel):

FILE: test_gnupg.py
  function skipIf (line 29) | def skipIf(condition, message):
  function is_list_with_len (line 213) | def is_list_with_len(o, n):
  function get_key_data (line 220) | def get_key_data(s):
  function compare_keys (line 230) | def compare_keys(k1, k2):
  function prepare_homedir (line 249) | def prepare_homedir(hd):
  class GPGTestCase (line 262) | class GPGTestCase(unittest.TestCase):
    method setUp (line 264) | def setUp(self):
    method tearDown (line 289) | def tearDown(self):
    method test_environment (line 295) | def test_environment(self):
    method test_list_keys_initial (line 300) | def test_list_keys_initial(self):
    method generate_key (line 308) | def generate_key(self, first_name, last_name, domain, passphrase=None,...
    method do_key_generation (line 333) | def do_key_generation(self):
    method test_key_generation_with_invalid_key_type (line 339) | def test_key_generation_with_invalid_key_type(self):
    method test_key_generation_with_colons (line 357) | def test_key_generation_with_colons(self):
    method test_key_generation_with_escapes (line 380) | def test_key_generation_with_escapes(self):
    method test_key_generation_failure (line 403) | def test_key_generation_failure(self):
    method test_key_generation_input (line 425) | def test_key_generation_input(self):
    method test_add_subkey (line 449) | def test_add_subkey(self):
    method test_add_subkey_with_invalid_key_type (line 473) | def test_add_subkey_with_invalid_key_type(self):
    method test_deletion_subkey (line 490) | def test_deletion_subkey(self):
    method test_list_subkey_after_generation (line 533) | def test_list_subkey_after_generation(self):
    method test_list_keys_after_generation (line 584) | def test_list_keys_after_generation(self):
    method test_key_trust (line 709) | def test_key_trust(self):
    method test_list_signatures (line 740) | def test_list_signatures(self):
    method test_scan_keys (line 754) | def test_scan_keys(self):
    method test_scan_keys_mem (line 787) | def test_scan_keys_mem(self):
    method test_quick_sign_key (line 802) | def test_quick_sign_key(self):
    method test_encryption_and_decryption (line 826) | def test_encryption_and_decryption(self):
    method test_import_and_export (line 962) | def test_import_and_export(self):
    method test_import_only (line 1010) | def test_import_only(self):
    method test_signature_verification (line 1030) | def test_signature_verification(self):
    method test_signature_file (line 1115) | def test_signature_file(self):
    method test_subkey_signature_file (line 1155) | def test_subkey_signature_file(self):
    method test_deletion (line 1194) | def test_deletion(self):
    method test_nogpg (line 1207) | def test_nogpg(self):
    method test_invalid_home (line 1213) | def test_invalid_home(self):
    method test_make_args (line 1221) | def test_make_args(self):
    method do_file_encryption_and_decryption (line 1228) | def do_file_encryption_and_decryption(self, encfname, decfname):
    method test_file_encryption_and_decryption (line 1283) | def test_file_encryption_and_decryption(self):
    method test_invalid_outputs (line 1293) | def test_invalid_outputs(self):
    method test_filenames_with_spaces (line 1357) | def test_filenames_with_spaces(self):  # See Issue #16
    method test_search_keys (line 1369) | def test_search_keys(self):  # pragma: no cover
    method test_quote_with_shell (line 1382) | def test_quote_with_shell(self):
    method disabled_test_signing_with_uid (line 1408) | def disabled_test_signing_with_uid(self):  # pragma: no cover
    method test_doctest_import_keys (line 1420) | def test_doctest_import_keys(self):
    method test_recv_keys_no_server (line 1516) | def test_recv_keys_no_server(self):
    method test_invalid_fileobject (line 1521) | def test_invalid_fileobject(self):
    method remove_all_existing_keys (line 1532) | def remove_all_existing_keys(self):
    method test_no_such_key (line 1543) | def test_no_such_key(self):
    method test_get_recipients (line 1559) | def test_get_recipients(self):
    method test_passing_paths (line 1575) | def test_passing_paths(self):
    method test_multiple_signatures (line 1627) | def test_multiple_signatures(self):
    method test_multiple_signatures_one_invalid (line 1649) | def test_multiple_signatures_one_invalid(self):
    method test_auto_key_locating (line 1678) | def test_auto_key_locating(self):
    method test_passphrase_encoding (line 1687) | def test_passphrase_encoding(self):
    method test_configured_group (line 1690) | def test_configured_group(self):
    method test_exception_propagation (line 1699) | def test_exception_propagation(self):
  function suite (line 1734) | def suite(args=None):
  function init_logging (line 1750) | def init_logging():
  function main (line 1766) | def main():
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (335K chars).
[
  {
    "path": ".coveragerc",
    "chars": 122,
    "preview": "[run]\nbranch = True\nomit =\n    /opt/python/*\n\n[report]\nexclude_lines =\n    pragma: no cover\n    raise NotImplementedErro"
  },
  {
    "path": ".flake8",
    "chars": 138,
    "preview": "[flake8]\nmax-line-length=120\nignore =\n    E731\n    W504\n\nexclude =\n    build\n    .tox\n\nper-file-ignores =\n    docs/conf."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 646,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve this library.\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/workflows/python-package.yml",
    "chars": 1990,
    "preview": "name: Tests\n\non:\n  push:\n    branches: [ master ]\n    paths-ignore:\n      - 'LICENSE.*'\n      - 'README.*'\n      - '.git"
  },
  {
    "path": ".gitignore",
    "chars": 208,
    "preview": "build\ndist\nkeys\n.tox\n.egg-info\nDETAILS\nMANIFEST\nrandom_binary_data\n__pycache__\n*.pyc\n*.log\n.dict-validwords\ndocs/themes/"
  },
  {
    "path": ".hgignore",
    "chars": 164,
    "preview": "(build|dist|keys|\\.(tox|egg-info))/\n(DETAILS|MANIFEST|random_binary_data|watcher.conf|hover.json)$\n\\.(pyc|log|dict-valid"
  },
  {
    "path": ".hgtags",
    "chars": 1036,
    "preview": "afbe4e74cfc7fb79dde344499e8934c88874ef85 0.3.6\n1979c07150aa6c7cb16b7f70db71f1b973153b93 0.3.7\n7a54f558eb05e2b0ff25b51f43"
  },
  {
    "path": ".readthedocs.yaml",
    "chars": 572,
    "preview": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html f"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1468,
    "preview": "Copyright (c) 2008-2022 by Vinay Sajip.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or"
  },
  {
    "path": "MANIFEST.in",
    "chars": 107,
    "preview": "include LICENSE.txt\ninclude README.rst\ninclude test_gnupg.py\ninclude messages.json\ninclude test_*ring.gpg\n\n"
  },
  {
    "path": "README.rst",
    "chars": 21473,
    "preview": "|badge1| |badge2| |badge3|\n\n.. |badge1| image:: https://img.shields.io/github/actions/workflow/status/vsajip/python-gnup"
  },
  {
    "path": "docs/Makefile",
    "chars": 4812,
    "preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD "
  },
  {
    "path": "docs/_static/sidebar.js",
    "chars": 4916,
    "preview": "/*\n * sidebar.js\n * ~~~~~~~~~~\n *\n * This script makes the Sphinx sidebar collapsible.\n *\n * .sphinxsidebar contains .sp"
  },
  {
    "path": "docs/_templates/page.html",
    "chars": 1221,
    "preview": "{% extends \"!page.html\" %}\n\n{% block body %}\n{{ super() }}\n<!-- div id=\"disqus_thread\"></div>\n<script type=\"text/javascr"
  },
  {
    "path": "docs/conf.py",
    "chars": 9604,
    "preview": "# -*- coding: utf-8 -*-\n#\n# GnuPG Wrapper for Python documentation build configuration file, created by\n# sphinx-quickst"
  },
  {
    "path": "docs/index.rst",
    "chars": 72386,
    "preview": ".. GnuPG Wrapper for Python documentation master file, created by\n   sphinx-quickstart on Thu Jul 02 16:14:12 2009.\n\n###"
  },
  {
    "path": "docs/requirements.txt",
    "chars": 63,
    "preview": "sphinxcontrib-spelling==7.6.2\nsphinx<7\nsphinx-rtd-theme>=1.2.2\n"
  },
  {
    "path": "docs/spelling_wordlist.txt",
    "chars": 415,
    "preview": "Cunnane\nFolkinshteyn\nDmitry\nGladkov\nAbdul\nKarim\nYann\nLeboulanger\nKirill\nYakovenko\nLeftwich\nMichal\nNiklas\nNoël\nJannis\nLei"
  },
  {
    "path": "gnupg.py",
    "chars": 87209,
    "preview": "\"\"\" A wrapper for the GnuPG `gpg` command.\n\nPortions of this module are derived from A.M. Kuchling's well-designed\nGPG.p"
  },
  {
    "path": "messages.json",
    "chars": 19992,
    "preview": "{\n  \"0000\": \"Success\",\n  \"0001\": \"General error\",\n  \"0002\": \"Unknown packet\",\n  \"0003\": \"Unknown version in packet\",\n  \""
  },
  {
    "path": "package.json",
    "chars": 2320,
    "preview": "{\n  \"index-metadata\": {\n    \"extensions\": {\n      \"python.details\": {\n        \"classifiers\": [\n          \"Development St"
  },
  {
    "path": "pyproject.toml",
    "chars": 94,
    "preview": "[build-system]\nrequires = [\n    \"setuptools >= 44\",\n]\nbuild-backend = 'setuptools.build_meta'\n"
  },
  {
    "path": "release",
    "chars": 2536,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright (C) 2023 Red Dove Consultants Limited\n#\nimport argparse\nimp"
  },
  {
    "path": "setup.cfg",
    "chars": 2082,
    "preview": "[metadata]\nname = python-gnupg\nversion = attr: gnupg.__version__\ndescription = A wrapper for the Gnu Privacy Guard (GPG "
  },
  {
    "path": "test_gnupg.py",
    "chars": 82788,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nA test harness for gnupg.py.\n\nCopyright (C) 2008-2026 Vinay Sajip. All rights reserved.\n\"\"\"\n"
  },
  {
    "path": "tox.ini",
    "chars": 2783,
    "preview": "# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will "
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the vsajip/python-gnupg GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (314.2 KB), approximately 83.6k tokens, and a symbol index with 187 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!