summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKali Kaneko <kali@futeisha.org>2014-07-02 11:38:15 -0500
committerKali Kaneko <kali@futeisha.org>2014-07-02 11:38:15 -0500
commitd1955bd267a132c24d9e64dde7a1cdb8bd9fe9c5 (patch)
tree241d30509e30704431ddd378883e477eaa225a07
parentbc02d9b78a274f3d2a73c63473bc6b28cfcc5f8d (diff)
Imported Upstream version 1.2.6
-rw-r--r--LICENSE663
-rw-r--r--PKG-INFO28
-rw-r--r--README71
-rw-r--r--docs/Complete-Guide-to-Publishing-OpenPGP-in-DNS.md832
-rw-r--r--docs/DETAILS1225
-rw-r--r--docs/Makefile153
-rw-r--r--docs/NOTES-python-gnupg-3.1-audit.html946
-rw-r--r--docs/NOTES-python-gnupg-3.1-audit.org232
-rw-r--r--docs/NOTES-python-openpgp-implementations.txt31
-rw-r--r--docs/OpenPGP-keys-in-DNS.md133
-rw-r--r--docs/_static/DETAILS.html2677
-rw-r--r--docs/_static/agogo.css337
-rw-r--r--docs/_static/pygments.css69
-rw-r--r--docs/change-license-emails.txt272
-rw-r--r--docs/conf.py312
-rw-r--r--docs/gnupg.rst131
-rw-r--r--docs/gpg-migrate.txt208
-rw-r--r--docs/index.rst44
-rw-r--r--docs/make.bat190
-rw-r--r--gnupg/__init__.py47
-rw-r--r--gnupg/_ansistrm.py172
-rw-r--r--gnupg/_logger.py99
-rw-r--r--gnupg/_meta.py871
-rw-r--r--gnupg/_parsers.py1385
-rw-r--r--gnupg/_trust.py103
-rw-r--r--gnupg/_util.py617
-rw-r--r--gnupg/_version.py11
-rw-r--r--gnupg/copyleft.py749
-rw-r--r--gnupg/gnupg.py1067
-rw-r--r--requirements.txt3
-rw-r--r--setup.cfg12
-rw-r--r--setup.py133
-rw-r--r--versioneer.py656
33 files changed, 14479 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ab77dae
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,663 @@
+This file is part of python-gnupg, a python wrapper for GnuPG.
+
+Licensed GNU GENERAL PUBLIC LICENSE 3+.
+Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+ © 2013 Andrej B.
+ © 2013 LEAP Encryption Access Project
+ © 2008-2012 Vinay Sajip
+ © 2005 Steve Traugott
+ © 2004 A.M. Kuchling
+All rights reserved.
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+
+ BEGIN ORIGINAL LICENSE TEXT
+
+Copyright (c) 2008-2012 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.
+
+ END ORIGINAL LICENSE TEXT \ No newline at end of file
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..bf2840d
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,28 @@
+Metadata-Version: 1.1
+Name: gnupg
+Version: 1.2.6-6-g6fa8b59
+Summary: A Python wrapper for GnuPG
+Home-page: https://github.com/isislovecruft/python-gnupg
+Author: Isis Agora Lovecruft
+Author-email: isis@patternsinthevoid.net
+License: GPLv3+
+Download-URL: https://github.com/isislovecruft/python-gnupg/archive/master.zip
+Description: This module allows easy access to GnuPG's key management, encryption and signature functionality from Python programs, by interacting with GnuPG through file descriptors. Input arguments are strictly checked and sanitised, and therefore this module should be safe to use in networked applications requiring direct user input. It is intended for use with Python 2.6 or greater.
+
+Platform: Linux
+Platform: BSD
+Platform: OSX
+Platform: Windows
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Security :: Cryptography
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Utilities
diff --git a/README b/README
new file mode 100644
index 0000000..11e0f78
--- /dev/null
+++ b/README
@@ -0,0 +1,71 @@
+# python-gnupg #
+
+Fork of [python-gnupg-0.3.2](https://code.google.com/p/python-gnupg/), patched
+to fix a potential vulnerability which could result in remote code execution,
+do to unsanitised inputs being passed to ```subprocess.Popen([...], shell=True)```.
+
+### Installation ###
+
+#### From [PyPI](https://pypi.python.org) ####
+It's simple. Just do:
+```
+[sudo] pip install gnupg
+```
+
+#### From this git repository ####
+To install this package from this git repository, do:
+
+```
+git clone https://github.com/isislovecruft/python-gnupg.git
+cd python-gnupg
+make install
+make test
+```
+
+Optionally to build the documentation after installation, do:
+```
+make docs
+```
+
+To get started using python-gnupg's API, see the [online documentation](https://python-gnupg.readthedocs.org/en/latest/),
+and import the module like so:
+```
+>>> import gnupg
+```
+
+The primary interface class you'll likely want to interact with is
+[```gnupg.GPG```](https://python-gnupg.readthedocs.org/en/latest/gnupg.html#gpg):
+```
+>>> gpg = gnupg.GPG(gpgbinary='/usr/bin/gpg',
+... gpghome='./keys',
+... pubring='pubring.gpg',
+... secring='secring.gpg')
+>>> batch_key_input = gpg.gen_key_input()
+>>> print batch_key_input
+Key-Type: RSA
+Name-Email: isis@wintermute
+Name-Comment: Generated by gnupg.py
+Key-Length: 4096
+Name-Real: Autogenerated Key
+%pubring /home/isis/code/python-gnupg/keys/pubring.gpg
+%secring /home/isis/code/python-gnupg/keys/secring.gpg
+%commit
+
+>>> key = gpg.gen_key(batch_key_input)
+>>> print key.fingerprint
+245D8FA30F543B742053949F553C0E154F2E7A98
+
+```
+
+### Bug Reports & Feature Requests ###
+
+Currently, the bugtracker is
+[here](https://github.com/isislovecruft/python-gnupg/issues) on Github. This
+may change in the future, but for now please feel free to use it to make
+bugreports and feature requests.
+
+Public comments and discussions are also welcome on the bugtracker, or as
+[tweets](https://twitter.com/isislovecruft).
+
+Patches are greatly appreciated, and if unsuitable for merging I will make
+improvement suggestions based on code review until the patch is acceptable.
diff --git a/docs/Complete-Guide-to-Publishing-OpenPGP-in-DNS.md b/docs/Complete-Guide-to-Publishing-OpenPGP-in-DNS.md
new file mode 100644
index 0000000..5112c97
--- /dev/null
+++ b/docs/Complete-Guide-to-Publishing-OpenPGP-in-DNS.md
@@ -0,0 +1,832 @@
+<!--
+
+Livejournal Introduction:
+
+I recently wrestled with something, learned quite a lot, and came up with a document that I'm really rather proud of, that
+shares knowledge that's not all out there in one place anywhere else. Along the way I've written some software that I'm
+releasing, that makes all of what I've learned a lot easier, and may help make the world a little more secure. I'd like to
+share it here.
+
+This is going to be a technical post. For that I apologize. The target of this post is anyone who has a GPG key that they'd
+like to expand to a greater audience, and who controls DNS for any of the email domains they publish. Anyone that I host DNS
+or mail for is also welcome to do this, if you use PGP, as part of the goal of writing this is to encourage adoption and use
+of these methods
+
+<lj-cut text="This will be long and technical">
+-->
+
+# The complete guide to publishing PGP keys in DNS
+
+## Introduction
+
+Publishing PGP keys is a pain. There are many disjoint keyservers, three or
+four _networks_ of which, which do (or don't) share information with each
+other. Some are corporate, some are private. And it's a crapshoot as to
+whose key is going to be on which, or worse, which will have the latest copy
+of a person's key.
+
+For a long time, GPG has had a way to publish keys in DNS, but it hasn't been
+well documented. This document hopes to change that.
+
+After reading this, you should:
+
+* Know the three ways to publish a key
+
+* Have at least a couple tools to do so
+
+* Have learned a bit more about DNS
+
+The target audience for this guide is a technical one. It's expected you
+understand what DNS is, and what an RFC and a resource record is.
+
+There are three ways to publish a PGP key in DNS. Most modern versions of GPG
+can retrieve from all three, although it's not enabled by default. There are
+no compile-time options you need to enable it, and it's simple to turn on. Of
+the three key-publishing methods, there are two that you probably shouldn't
+use at the same time, and there are advantages and disadvantages to each,
+which I hope to outline below, both in general and for each method.
+
+### Advantages to DNS publishing of your keys
+
+* It's universal. Your DNS is your own, and you don't have to worry about
+ which network of vastly-disconnectedkeyservers is caching your key.
+
+* Using DNS does not stop you from publishing via other means.
+
+* If you run an organization, you can easily publish all your employee-keys
+ via this method, and in the same step,define a signing-policy, such that a
+ person need only assign trust to your organization's "keysigning key" (or
+ theCEO's key, or the CTO's), without the trouble of running a keyserver.
+
+* DNSSEC can be (somewhat) used as an additional trust-path vector. More on
+ this in the notes at the bottom.
+
+* You do not have to be searching DNS for keys in order to publish. On the
+ same note, you do not have to be publishing in this manner to search
+ forothers there.
+
+### Disadvantages to DNS publishing
+
+* If you don't control your own DNS (or have a good relationship with your DNS
+ admin), this isn't going to beas easy or even possible. Ideally, you want
+ to be running BIND.
+
+* With two of the three methods listed here, you're going to need to be able
+ to put a CERT record into your DNS. Mostweb-enabled DNS tools probably will
+ not give you this ability. The third uses TXT records, which SPF has caused
+ to befairly universal in web-interfaces. However, it's also the least
+ standards-defined of the three.
+
+* Using at least some of these methods, it's not always a "set it and forget
+ it" procedure. You may need toperiodically re-export your key and
+ re-publish it, especially if you gain new signatures.
+
+* Using some of these methods, you're going to be putting some pretty large,
+ pretty unwiedly lines in your DNS zones. Not everyone will easily be able
+ to retrieve them, but again, you can still publish other ways.
+
+* Using some of these methods, DNS is just a means to an end: you still need
+ to publish your key elsewhere, like a webpage,and the DNS records just point
+ at it.
+
+* Initial verifications of most of these seem to imply that only DSA keys are
+ supported, although I welcome feedback. Itseems the community is trying to
+ get RSA keys to make a comeback. They're the only type supported by the
+ gpg2.0 card, andthey are the default keytype. There was a while where they
+ weren't, though. Since writing this document, I've discoveredthat "new" RSA
+ keys work, but ancient RSA keys with no subkeys tend to misbehave.
+
+### Turning on key-fetching via DNS
+
+Inside your GPG "options" file, find the "auto-key-locate" line, and add
+"cert" and/or "pka" to the options.
+
+ auto-key-locate cert pka (as well as other methods, like keyserver URLs)
+
+
+Don't be surprised if a lot of people don't use this method.
+
+Note that you can also turn on two options during signature verification.
+They are specified in a "verify-options" clause in your config file, or on the
+command line, and they are (right from the GPG manpage):
+
+ pka-lookups
+
+ Enable PKA lookups to verify sender addresses. Note that
+ PKA is based on DNS, and so enabling this option may dis-
+ close information on when and what signatures are veri-
+ fied or to whom data is encrypted. This is similar to the
+ "web bug" described for the auto-key-retrieve feature.
+
+And:
+
+ pka-trust-increase
+
+ Raise the trust in a signature to full if the signature
+ passes PKA validation. This option is only meaningful if
+ pka-lookups is set.
+
+
+You can also use the same options on the command line (as you'll see in this
+document).
+
+## Types of PGP Key Records
+
+### DNS PKA Records
+
+Relevant RFCs: None that I can find.
+
+Other Docs: The GPG source and mailing lists.
+
+#### Advantages
+
+* It's a TXT record. Easy to put in a zonefile with most management software.
+* No special tools required to generate, just three simple pieces of data.
+* Since it uses a special subzone, you can manage the _pka namespace in a
+ separate zonefile.
+* GPG has an option, when verifying a signature, to look up these records
+ (--verify-options pka-lookups), so it's doubly useful, both from a
+ distribution and a verification point.
+
+#### Disadvantages
+
+* As with IPGP certs, you're at the mercy of the URL. This doesn't put your
+ key in DNS, just the location of it, and the fingerprint. Some clients may
+ not be able to support https or http 1.1.
+* Not RFC standard.
+
+#### Howto
+
+1. Figure out which key you want to export:
+
+ %gpg --list-keys danm@prime.gushi.org
+ Warning: using insecure memory!
+ pub 1024D/624BB249 2000-10-02 &lt;-- I'm going to use this one.
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ uid Daniel Mahoney (Secondary Email) &lt;gushi@gushi.org&gt;
+ sub 2048g/DE20C529 2000-10-02
+ pub 1024R/309C17C5 1997-05-08
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+
+2. Export the key to a file (I use keyid.pub.asc, but it can be anything)
+
+ %gpg --export --armor 624BB249 &gt; 624BB249.pub.asc
+ Warning: using insecure memory!
+ %
+
+3. Get the fingerprint for your key:
+
+ %gpg --list-keys --fingerprint 624BB249
+ gpg: WARNING: using insecure memory!
+ gpg: please see http://www.gnupg.org/faq.html for more information
+ pub 1024D/624BB249 2000-10-02
+ Key fingerprint = C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249 &lt;-- That bit is your fingerprint.
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ uid Daniel Mahoney (Secondary Email) &lt;gushi@gushi.org&gt;
+ sub 2048g/DE20C529 2000-10-02
+
+4. Copy the file somewhere, like your webspace. It need not live on the same
+ server. It needs to be accessable by the url you create in the next step.
+
+ %cp 624BB249.pub.asc public_html/danm.pubkey.txt
+
+5. Make up your text record. The format is:
+
+ danm._pka.prime.gushi.org. TXT "v=pka1;fpr=C2063054549295F3349037FFFBBE5A30624BB249;uri=http://prime.gushi.org/danm.pubkey.txt"
+
+
+We'll take this in several parts. The record label is simply the email
+address with "._pka." replacing the "@". danm@prime.gushi.org becomes
+danm._pka.prime.gushi.org. Don't forget the trailing dot, if you're using the
+fully qualified name. I recommend sticking with fully-qualified, for
+simplicity.
+
+The body of the record is also simple. The v portion is just a version.
+There's only one version as far as I can tell, 'pka1'. The fpr is the
+fingerprint, with all whitespace stripped, and in uppercase. The uri is the
+location a key can be retrieved from. All the "names" are lowercase,
+separated by semicolons.
+
+6. Publish the above record in your DNS. Bump your serial number and reload
+ your nameserver. If you're using DNSSEC, re-sign your zone.
+
+#### Testing
+
+Most of the tests we're going to do for these are essentially the same
+activity. See if our DNS server is handing out an answer, and then see if GPG
+can retrieve it.
+
+1. A simple dig:
+
+ %dig +short danm._pka.prime.gushi.org. TXT
+ "v=pka1\;fpr=C2063054549295F3349037FFFBBE5A30624BB249\;uri=http://prime.gushi.org/danm.pubkey.txt"
+
+(The backslashes before the semicolons are normal). Other than that, it seems
+to make sense and match what I put in.)
+
+2. Test it with GPG. Rather than messing around with, and adding-from and
+ deleting from live keyrings, you can do:
+
+ %echo "foo" | gpg --no-default-keyring --keyring /tmp/gpg-$$ --encrypt --armor --auto-key-locate pka -r you@you.com
+
+
+(where you@you.com is the address of your primary key.) The /tmp/gpg-$$
+creates a random file named after your PID. What you should see, and what I
+see, is something like this:
+
+ gpg: WARNING: using insecure memory!
+ gpg: please see http://www.gnupg.org/faq.html for more information
+ gpg: keyring `/tmp/gpg-39996' created
+ gpg: requesting key 624BB249 from http server prime.gushi.org
+ gpg: key 624BB249: public key "Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;" imported
+ gpg: public key of ultimately trusted key CF45887D not found
+ gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
+ gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
+ gpg: Total number processed: 1
+ gpg: imported: 1
+ gpg: automatically retrieved `danm@prime.gushi.org' via PKA
+ gpg: DE20C529: There is no assurance this key belongs to the named user
+ pub 2048g/DE20C529 2000-10-02 Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ Primary key fingerprint: C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249
+ Subkey fingerprint: CE40 B786 81E2 5CB9 F7D3 1318 9488 EB58 DE20 C529
+ It is NOT certain that the key belongs to the person named
+ in the user ID. If you *really* know what you are doing,
+ you may answer the next question with yes.
+ Use this key anyway? (y/N) y
+ -----BEGIN PGP MESSAGE-----
+ Version: GnuPG v1.4.10 (FreeBSD)
+ hQIOA5SI61jeIMUpEAf/UotgWP8VQC9VTY36HaZeXO1CTFk90x0qlPrAhJk9YaoA
+ 2eHNKZSoHKqaLjzTbaWnWHnNZu0IllIS+qrAwNeIAhswfzDoc8Q9+/4sGSR3LmxA
+ 8SEwrJIvLmGVbqJEtnH8TTHIEao/lpL/d+ul4nLfbXRn0NW+MsaCAi8UsjbLlJeV
+ n4p0GQlpDoZCE55DTwMzfWMT84YVwuXTesuN+i7sSyJn2hT1rXuK1BCVcsgTcKdy
+ QhIo3EfKBlfFp74yiU7QCmlAujD6U6a93mmxezPIHVx/WGXgPExVRGgEzfT/tUcI
+ IQ2xMDUv4BF05hgm04GPGCbBY431j4UkdWWI6bvMLwgA2i01NmflH/6Z8+ss6J1M
+ e3RWnR7TPl5lDkXFBtLGAzO+HrsC5A32SbkTw+WsljCQLifJ2EalfoJ1QGY4Sp3v
+ H2YunwZLVPTc+D2JnrXfqNmi5zYZio8by3c8L0CgWdMwZ7PPxZpTOLN77/MIjBkJ
+ EBb8Z6SZCgzTIhN5z56ZgWFvmSKf1vKkeUcrgxMs+DnA+XqBMJ9w520JwoTLjJza
+ syrlYVhd+ktY21DYB9OJ5MZx2HMAtkUDRAzW1zoLcehk1kdZNzhpjU5hqSjT8/GN
+ trKFeqkmKemrq2GvMNyJyrEOB8e7KgbmXa95YKH0Wh2D4SWpXukegyCspmY4tDE+
+ uckaFSao+48g8D6vs1irGSxBRjyhD/jPDblrgpo=
+ =NbgW
+ -----END PGP MESSAGE-----
+
+The "insecure memory" warning is a silly warning that the only way to turn off is to run GPG setuid root.
+You can see in the output that the key comes from PKA.
+
+The "it is NOT certain" warning has nothing to do with the fact that it came
+from DNS. You will get that warning every time you use that key (or any gpg
+key) until you have edited it and assigned ownertrust to it, or until the key
+is signed with a trusted signature, either from your personal web of trust, or
+from a signing service like the pgp.com directory.
+
+3. Ask other people to run it for you and send you the resulting blob. You should be able to decrypt it with your private key.
+
+### PGP CERT Records
+
+Also known as: The "big" CERT record.
+
+Relevant RFCs: [RFC 2538](http://www.faqs.org/rfcs/rfc2538.html),
+[RFC 4398](http://www.faqs.org/rfcs/rfc4398.html), specifically sections 2.1
+and 3.3
+
+#### Advantages
+
+* DNS is all you need. You don't have to host the key elsewhere. As a DNS
+ nerd, this strikes me as very cool.
+
+* Suprisingly easy to verify with dig, if you have a base64 converter handy
+ (openssl includes one)
+
+#### Disadvantages
+
+* These records can get big. Really big. Especially if you have photo-ids on your keys. You can play with export-options to shrink it somewhat. Big dns packets may require EDNS, or dns-over-tcp, which not everyone supports, but support is becoming more widespread as a result of DNSSEC awareness.
+
+* Requires the make-dns-cert tool, which isn't built by default.
+
+* Requires you to have some control over your actual zonefile. Most control panels won't cut it.
+
+* Make-dns-cert currently generates a very ugly record for this.
+
+ #### How to
+
+1. As before, the first step is to figure out which key we want.
+
+ %gpg --list-keys danm@prime.gushi.org
+ Warning: using insecure memory!
+ pub 1024D/624BB249 2000-10-02 &lt;-- I'm going to use this one.
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ uid Daniel Mahoney (Secondary Email) &lt;gushi@gushi.org&gt;
+ sub 2048g/DE20C529 2000-10-02
+ pub 1024R/309C17C5 1997-05-08
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+
+2. We export the key, but this time, it needs to be binary.
+
+ %gpg --export 624BB249 &gt; 624BB249.pub.bin
+ Warning: using insecure memory!
+
+3. We run make-dns-cert on it. make-dns-cert comes with no manual or docs,
+ but running with -h gives you all the clue you need.
+
+ make-dns-cert
+ -f fingerprint
+ -u URL
+ -k key file
+ -n DNS name
+
+So then,
+
+ make-dns-cert -n danm.prime.gushi.org. -k 624BB249.pub.bin
+ <pre>`%make-dns-cert -n danm.prime.gushi.org. -k 624BB249.pub.bin
+ danm.prime.gushi.org. TYPE37 \# 1298 0003 0000 00 9901A20439D8DAF1110400F770EC6AA006076334BEC6DB6FBB237DC194BC0AB8
+ 302C8953F04C28FC2085235D4F10EFA027234FBD63D142CCADD5213AD2B79A22C89ED9B4138370D8220D0F987F993A5364A4A7AC3D42F3765C384
+ 71DDD0FF3372E4AE6F7BEE1E18EF464A0BEB5BBE860A08238891455EBE7CB53D567E981F78ADBD263206B0493ADCB74DD00A0FF0E9A1CD245415E
+ CEF59435162AFCE4CDD14BC70400EA38FF501256E773DEA299404854D99F4EDB2757AA911A9C77C68AB8D6622E517A556C43D21F0523C568F016C
+ D0DB89EF435F0D53B4E07434213F899E6578955DC2C147931E7B6901C9FD8A02705417D69A879B3CC196D2AC2EAEF311192EE89ABAF5A60942167
+ B4625735FCBDFB5DE0E3AC1236A53FA4D7CDD7D75F5DE85AF50400867D9546B28B79AF10541053CF4AB06A6171BFD21458BFD12AF1AE2B2401CAD
+ 8851661F8AF6602F80EDAC99C79616BE1F910F4156242003779C68D7A079A8B18F89DD293E1B247E7420471300A4A0730AA61DE281CCC211FC405
+ A0A8A79877999FF9042AD892AB927DA371E8883BBB370AB7A97841408C3486BB18598CF2559BB42844616E69656C20502E204D61686F6E6579203
+ C64616E6D407072696D652E67757368692E6F72673E884E04101102000E050239D8DAF1040B030102021901000A0910FBBE5A30624BB249FA2E00
+ 9B057503ED498695AE5ED73CA1B98EBAEE13F717E500A0921E0D92724459100266FEBBC29E911C8B0F530BB43244616E69656C204D61686F6E657
+ 920285365636F6E6461727920456D61696C29203C67757368694067757368692E6F72673E8860041311020020050245D49FD7021B23060B090807
+ 030204150208030416020301021E01021780000A0910FBBE5A30624BB249158400A082C8AF43DA8B85F740D6B1A6E9FF0B4490520B8C00A08F77D
+ 21FBF86C842963E8090DC0646D1DD7F95C9B9020D0439D8DAF4100800F64257B7087F081772A2BAD6A942F305E8F95311394FB6F16EB94B3820DA
+ 01A756A314E98F4055F3D007C6CB43A994ADF74C648649F80C83BD65E917D4A1D350F8F5595FDC76524F3D3D8DDBCE99E1579259CDFDB8AE744FC
+ 5FC76BC83C5473061CE7CC966FF15F9BBFD915EC701AAD35B9E8DA0A5723AD41AF0BF4600582BE5F488FD584E49DBCD20B49DE49107366B336C38
+ 0D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D334FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A
+ 70A72D686C40319C807297ACA950CD9969FABD00A509B0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E9320B3B00020207FF5E1A3C
+ C5DA00E1E94EC8EF6C7FE9B49D944C71D8BBC817DD8E64A7344B9E48392E0B833B3B1DB7E6D5A38BE2826DEF0060F78C6417871EAF1CFBCBC47D2
+ 7E93718D975E0A3A36D868C021D6B771740CE2918307D69D614BBF0632DC31932EA31397A7F3B04618C9A76C2F38265C7037E303EDD8AEF03D069
+ 208E3FE9C4EA77D83E6311ED36C013D58C54E914B263A459E22D463A0288510C4752B99C163EEA0A55686979691AB0D9F9AA0C06C834446D7A723
+ EC534D819301382621ACF8930C74E9FD28C8797718AEC2C30CF601E24194B799234104A3D6239657B1D4AD545BDAA637F61541435CB51B4D138FB
+ F55E1A9FD2EED860E4459D6795B6FCCA23155A8846041811020006050239D8DAF4000A0910FBBE5A30624BB249415A009E37BCFDC64E76CBF6A86
+ 82B85EA161BD1DFB793DF00A0C471BC7B9723535CD855D8FF1EB93F01E251B698
+ %
+
+The program prints that all on **one line**.
+
+Immediately, we notice a few things.
+
+* The record type isn't "CERT", it's "TYPE37". This confused me for a while until I discovered [RFC3597](http://www.faqs.org/rfcs/rfc3597.html) Basically, it's a way that a DNS server can handle a resource record it doesn't know about, by giving it some special fields like the "#", as well as a length (which is the 1298 you see there).
+
+* The rest of the record is on one line. I wrapped it for the purposes of brevity. If I were using this in a zonefile, I would need to be careful that I wrapped it on a byte-boundary (every two characters is a byte). If I miss the boundary, named will refuse to load it, dnssec-signzone won't touch it, etc.
+
+4. So the thing is ugly and you don't want to touch it. The easiest way to work with it is to drop all that into a file:
+
+ %make-dns-cert -n danm.prime.gushi.org. -k 624BB249.pub.bin &gt; 624BB249.big.cert
+
+
+5. And then either read it into your editor, or tack it on like this:
+
+ %cat 624BB249.big.cert &gt;&gt; your.zonefile
+
+Be sure to make a backup first. Either way, you never have to copy/paste the raw hex and worry about newlines being inserted where you don't want them.
+
+6. Before you reload your zone, you might want to use named-checkzone on it first:
+
+ prime# named-checkzone gushi.org gushi.org.hosts
+ zone gushi.org/IN: loaded serial 2009102909
+ OK
+ prime#
+
+7. Voice of experience: You may want to dial the TTL (which controls how long servers will cache your data) way down on the record above. It's not hard, just put a number before the TYPE37, with a space, i.e:
+
+ danm.prime.gushi.org. 30 TYPE37
+
+This way if it all goes terribly wrong, or you need to make changes, it won't be cached for very long.
+
+8. If it looks okay, bump your serial number and reload.
+
+#### Testing
+
+1. As above, you can dig, but you won't be able to easily read the results:
+
+ prime# dig +short danm.prime.gushi.org CERT
+ ;; Truncated, retrying in TCP mode.
+
+
+PGP 0 0
+mQGiBDnY2vERBAD3cOxqoAYHYzS+xttvuyN9wZS8CrgwLIlT8Ewo/CCF
+I11PEO+gJyNPvWPRQsyt1SE60reaIsie2bQTg3DYIg0PmH+ZOlNkpKes
+PULzdlw4Rx3dD/M3Lkrm977h4Y70ZKC+tbvoYKCCOIkUVevny1PVZ+mB
+94rb0mMgawSTrct03QCg/w6aHNJFQV7O9ZQ1Fir85M3RS8cEAOo4/1AS
+Vudz3qKZQEhU2Z9O2ydXqpEanHfGirjWYi5RelVsQ9IfBSPFaPAWzQ24
+nvQ18NU7TgdDQhP4meZXiVXcLBR5Mee2kByf2KAnBUF9aah5s8wZbSrC
+6u8xEZLuiauvWmCUIWe0Ylc1/L37XeDjrBI2pT+k183X119d6Fr1BACG
+fZVGsot5rxBUEFPPSrBqYXG/0hRYv9Eq8a4rJAHK2IUWYfivZgL4DtrJ
+nHlha+H5EPQVYkIAN3nGjXoHmosY+J3Sk+GyR+dCBHEwCkoHMKph3igc
+zCEfxAWgqKeYd5mf+QQq2JKrkn2jceiIO7s3CrepeEFAjDSGuxhZjPJV
+m7QoRGFuaWVsIFAuIE1haG9uZXkgPGRhbm1AcHJpbWUuZ3VzaGkub3Jn
+PohOBBARAgAOBQI52NrxBAsDAQICGQEACgkQ+75aMGJLskn6LgCbBXUD
+7UmGla5e1zyhuY667hP3F+UAoJIeDZJyRFkQAmb+u8KekRyLD1MLtDJE
+YW5pZWwgTWFob25leSAoU2Vjb25kYXJ5IEVtYWlsKSA8Z3VzaGlAZ3Vz
+aGkub3JnPohgBBMRAgAgBQJF1J/XAhsjBgsJCAcDAgQVAggDBBYCAwEC
+HgECF4AACgkQ+75aMGJLskkVhACggsivQ9qLhfdA1rGm6f8LRJBSC4wA
+oI930h+/hshClj6AkNwGRtHdf5XJuQINBDnY2vQQCAD2Qle3CH8IF3Ki
+utapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz0AfGy0OplK33TGSG
+SfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRPxfx2vIPF
+RzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvN
+ILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjT
+NP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM
+2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/9e
+GjzF2gDh6U7I72x/6bSdlExx2LvIF92OZKc0S55IOS4Lgzs7Hbfm1aOL
+4oJt7wBg94xkF4cerxz7y8R9J+k3GNl14KOjbYaMAh1rdxdAzikYMH1p
+1hS78GMtwxky6jE5en87BGGMmnbC84JlxwN+MD7diu8D0Gkgjj/pxOp3
+2D5jEe02wBPVjFTpFLJjpFniLUY6AohRDEdSuZwWPuoKVWhpeWkasNn5
+qgwGyDREbXpyPsU02BkwE4JiGs+JMMdOn9KMh5dxiuwsMM9gHiQZS3mS
+NBBKPWI5ZXsdStVFvapjf2FUFDXLUbTROPv1Xhqf0u7YYORFnWeVtvzK
+IxVaiEYEGBECAAYFAjnY2vQACgkQ+75aMGJLsklBWgCeN7z9xk52y/ao
+aCuF6hYb0d+3k98AoMRxvHuXI1Nc2FXY/x65PwHiUbaY
+
+
+It's still ugly, but it's not AS ugly because it's base64, which includes
+spaces, at least, and is easier to search for a pattern. Base64 can also be
+easily wrapped on any boundary, which is nice.
+
+You can run your existing exported key through a base64 converter, like the
+one built into the openssl binary, if you want to compare:
+
+ %cat 624BB249.pub.bin | openssl enc -base64
+ mQGiBDnY2vERBAD3cOxqoAYHYzS+xttvuyN9wZS8CrgwLIlT8Ewo/CCFI11PEO+g
+ JyNPvWPRQsyt1SE60reaIsie2bQTg3DYIg0PmH+ZOlNkpKesPULzdlw4Rx3dD/M3
+ Lkrm977h4Y70ZKC+tbvoYKCCOIkUVevny1PVZ+mB94rb0mMgawSTrct03QCg/w6a
+ (...etc...)
+ OPv1Xhqf0u7YYORFnWeVtvzKIxVaiEYEGBECAAYFAjnY2vQACgkQ+75aMGJLsklB
+ WgCeN7z9xk52y/aoaCuF6hYb0d+3k98AoMRxvHuXI1Nc2FXY/x65PwHiUbaY
+
+
+Now, while you could compare things byte-by-byte here, what I've done as a
+"casual check" is just pick random strings in the text and see if they match
+up. For example, you can see that "reaIsie2" is present in both. They both
+start with and end with similar strings on every line. The real test, of
+course, is to see if GPG recognizes it as a valid key.
+
+By the way, since I use DNSSEC, dnssec-signzone rewrites this record into the
+proper "presentation format" for me, which is base64. If you want a similar
+function, you can use named-compilezone to get some of the same effects, or
+you can use the shell script I provide later in this document, with which you
+don't even need make-dns-cert.
+
+2. Testing with gpg
+
+As above, the command to test this is remarkably simple:
+
+ %rm /tmp/gpg-*
+ %echo "foo" | gpg --no-default-keyring --keyring /tmp/gpg-$$ --encrypt --armor --auto-key-locate cert -r danm@prime.gushi.org
+ gpg: keyring `/tmp/gpg-39996' created
+ gpg: key 624BB249: public key "Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;" imported
+ gpg: Total number processed: 1
+ gpg: imported: 1
+ gpg: automatically retrieved `danm@prime.gushi.org' via DNS CERT
+ gpg: DE20C529: There is no assurance this key belongs to the named user
+ pub 2048g/DE20C529 2000-10-02 Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ Primary key fingerprint: C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249
+ Subkey fingerprint: CE40 B786 81E2 5CB9 F7D3 1318 9488 EB58 DE20 C529
+ It is NOT certain that the key belongs to the person named
+ in the user ID. If you *really* know what you are doing,
+ you may answer the next question with yes.
+ Use this key anyway? (y/N) y
+ -----BEGIN PGP MESSAGE-----
+ Version: GnuPG v1.4.10 (FreeBSD)
+ hQIOA5SI61jeIMUpEAf/Sx7MKWm+e9EpUTSrDaBp4nJfDcBeqbYJulPRbDZz7eVW
+ 2+ol6sG0jWjuirbG1YppZccEr9mgqaQujdSXb/bleD8POS0TEWuf3aPswFQvHf90
+ NLEzHt6BnfLoeobXXxyCflNaGX8zW+XgJtwZqAc2+jietuz8MOUhrf5m17CsW/wZ
+ IuEqwaek+K1irJp+w3rhaE08Jzb/S4CCifeW9J3mK57chQoPOu7Nz3rY666YKp/3
+ 9T9StOgmFiNpvtFPNy4N7hHMHvbQwRsKlnkl+a7n0Aq2+OF4d1+/k2EE4uSGgcz0
+ oHvee8DnuOx3P92mO4Jz5/0O0lwBD7I51iOjzUurTAgAiIM5sHV8/QFCVzH9Ule+
+ gd8Wo5momcphkU/AXpce5Xgi/Vm4oGQ0x0queii8afUrzkpeN5SuwgQfAdOPiXW5
+ 2bo527jBllxOxjeBasfky82XheTnLzbAQNvQNTEM9zE7zCl1LQJUZEJ1hVzcOevI
+ s+cm/AaGII9VkrAtSt3aLSRZuRJHFmhGvYd2Hz5WzcV1YFjXXP1eLwfetDBlaeB9
+ /K5v4hZBkIZPbHX0DcLVrP96mCIT4wCBYSJw+I6n0E6Fz3IfybQG2HMfqWp966/c
+ 00ijx/aRDh42Dr/fTropuzzFzQr7weYDa1JnN3Zoftv6Zb/n+NcrmMiDCH8jJV6E
+ uMkaeeB5Mv7ssDQ9kPhO989CHFcznrE1lgOxjX8=
+ =NTLY
+ -----END PGP MESSAGE-----
+
+Okay, as above, try to decrypt that with your private key.
+
+### IPGP CERT Records
+
+Also known as: The "little" or "short" CERT record. (These terms are purely my
+own).
+
+Relevant RFCs: [RFC 2538](http://www.faqs.org/rfcs/rfc2538.html),
+[RFC 4398](http://www.faqs.org/rfcs/rfc4398.html), specifically sections 2.1
+and 3.3
+
+IPGP certs are interesting. It's basically the same pieces of infomation that
+are in the PKA record, as above, except that it's supported by an RFC.
+Despite the RFC compliance, I am not sure if any non-gpg client knows to look
+for them. However, because it's a DNS cert, make-dns-cert encodes the
+information in binary, and your DNS server will see it in base64. So
+verifying it visually is harder than verifying either of the above.
+
+#### Advantages
+
+* Small, easy-to-transmit records.
+* Can use the same uri as the PKA record.
+
+#### Disadvantages
+
+* Relies on the URI scheme. I haven't yet been able to get a definitive list
+ of what uri schemes are supported, although I've seen http and finger. I've
+ also seen reports that unless gpg is compiled against curl, http 1.1 is not
+ supported (what this actually means is that any host that supports SSL will
+ probably work, because of some of the nuances of SSL).
+* With PGP certs and IPGP certs, GPG will only parse the first key it gets, so
+ if you publish both, and one doesn't work, there's no failover. I've argued
+ that this should be fixed.
+* Requires make-dns-cert, which is not built in GPG by default. (But see "A
+ Better Way" below)
+* Requires publication in your main DNS zone.
+* Despite being RFC compliant, GPG has additional trust vectors for PKA but
+ not this, despite the fact that they share basically the same information.
+* Harder to verify with dig.
+
+#### Howto
+
+1. Note that some of these steps are redundant. If you're already doing a PKA
+ key, skip to step 5.
+
+2. Dig:
+
+ %gpg --list-keys danm@prime.gushi.org
+ Warning: using insecure memory!
+ pub 1024D/624BB249 2000-10-02 &lt;-- I'm going to use this one.
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ uid Daniel Mahoney (Secondary Email) &lt;gushi@gushi.org&gt;
+ sub 2048g/DE20C529 2000-10-02
+ pub 1024R/309C17C5 1997-05-08
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+
+3. Export the key to a file (I use keyid.pub.asc, but it can be anything)
+
+ %gpg --export --armor 624BB249 &gt; 624BB249.pub.asc
+ Warning: using insecure memory!
+ %
+
+4. Get the fingerprint for your key:
+
+ %gpg --list-keys --fingerprint 624BB249
+ gpg: WARNING: using insecure memory!
+ gpg: please see http://www.gnupg.org/faq.html for more information
+ pub 1024D/624BB249 2000-10-02
+ Key fingerprint = C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249 &lt;-- That bit is your fingerprint.
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ uid Daniel Mahoney (Secondary Email) &lt;gushi@gushi.org&gt;
+ sub 2048g/DE20C529 2000-10-02
+
+5. As above, run make-dns-cert. This time we use the -n, -f, and -u options:
+
+ %make-dns-cert -n danm.prime.gushi.org. -f C2063054549295F3349037FFFBBE5A30624BB249 -u http://prime.gushi.org/danm.pubkey.txt
+ danm.prime.gushi.org. TYPE37 \# 64 0006 0000 00 14 C2063054549295F3349037FFFBBE5A30624BB249 687474703A2F2F7072696D652E67757368692E6F72672F64616E6D2E7075626B65792E747874
+ %
+
+
+6. Put the above in DNS. All on one line. Optionally add a TTL.
+
+7. IMPORTANT: make sure you don't have any other CERT records with the same
+ label (i.e. a "big" cert, as above). While it won't break things, you have
+ no control over which (of multiple) people will get.
+
+8. Reload your zone, and test. Testing will probably look VERY MUCH like the
+ above, but here are the steps anyway:
+
+#### Testing
+
+1. Dig:
+
+ %dig +short danm.prime.gushi.org CERT
+ 6 0 0 FMIGMFRUkpXzNJA3//u+WjBiS7JJaHR0cDovL3ByaW1lLmd1c2hpLm9y Zy9kYW5tLnB1YmtleS50eHQ=
+
+Sadly, I haven't come across an easy way to decipher it yet, but there's
+always gpg.
+
+2. GPG:
+
+Since we're fetching the same kind of record, the command is exactly the same
+as before:
+
+ %echo "foo" | gpg --no-default-keyring --keyring /tmp/gpg-$$ --encrypt --armor --auto-key-locate cert -r danm@prime.gushi.org
+ gpg: WARNING: using insecure memory!
+ gpg: please see http://www.gnupg.org/faq.html for more information
+ gpg: keyring `/tmp/gpg-39996' created
+ gpg: requesting key 624BB249 from http server prime.gushi.org
+ gpg: key 624BB249: public key "Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;" imported
+ gpg: public key of ultimately trusted key CF45887D not found
+ gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
+ gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
+ gpg: Total number processed: 1
+ gpg: imported: 1
+ gpg: automatically retrieved `danm@prime.gushi.org' via DNS CERT
+ gpg: DE20C529: There is no assurance this key belongs to the named user
+ pub 2048g/DE20C529 2000-10-02 Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+ Primary key fingerprint: C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249
+ Subkey fingerprint: CE40 B786 81E2 5CB9 F7D3 1318 9488 EB58 DE20 C529
+ It is NOT certain that the key belongs to the person named
+ in the user ID. If you *really* know what you are doing,
+ you may answer the next question with yes.
+ Use this key anyway? (y/N) y
+ -----BEGIN PGP MESSAGE-----
+ Version: GnuPG v1.4.10 (FreeBSD)
+ hQIOA5SI61jeIMUpEAgApZurJi3hZmDaUFjB2j93eX/lTl96xq6T//sz6nT6jcTx
+ IPnq1RN8IrIQPjDBByHdqOZBT5hhblr9xi7NKIIv3W4q4L0z0fJx7NERPZNvn/H0
+ DkTwfDgAvCRxcKjenpLSwKZFwLjyfS7wjlDr3HFX7Tila0hbzplHslvgTE0QMcd7
+ 7oNmEyOL3z+yZr/afQGp2wpzDv4YB9zOiNHcHcenqX0yrtiqKozZ9VAldi53rb/q
+ f38lwInbveyAcEQkE2iFwhRsbMR4VLcsBoxY6D9brsBprt23ey8Rnv+bQ9IAR0VN
+ /WYzU4zUUqb8HmpNFXQLEgH8A2BENw+bxkVYHjSfWQf/cBSGAzfBQQVJ7qp4tN0Z
+ FRVe51dokbU4NM9tGBdCzFHWARVkQX/Ulekd4F3sxBR/sum1UOT2xl2THVBz7/Pq
+ UCrTRPA0uH4dIbL5JpfGZhqsJ079+wmUWUtJIiO2wXi7ePEA/DrBC6p7jlmjyYN/
+ AeSKcPoTeLX+zryV5bECx4RO6S56EEcy0Ns0pASGMsgUnKL6Adrv3Y6ea3ZAOQMn
+ H9Uo28BKTKNUvUaBpN8cV8jIbKYPPW9i04kvEQRqs5rdamERCY1vVTqYTrcLsNqz
+ fF3KopX+V82X1oE2QuGdFfd8mK57ZXJL3VRUrfohQjhfYNKzougiP46rQQv79MYT
+ j8kazWyJUuufm6NVco1/35Zdp1UhHu8qTgXxrjo=
+ =zY9G
+ -----END PGP MESSAGE-----
+ %
+
+Strangely, the output doesn't say what PKA does (a PKA retrieval has a line
+about fetching via HTTP), however, by checking my webserver logs, I can see it
+retrieved it from there:
+
+ %tail -200 /usr/local/apache/logs/prime.gushi.org.log | grep pubkey | tail -1
+ prime.gushi.org 72.9.101.130 - - [28/Oct/2009:23:50:43 -0400] "GET /danm.pubkey.txt HTTP/1.1" 200 4337 "-" "-"
+ %
+
+As usual, test decryption, etc. You're done.
+
+## Further Steps
+
+* Figure out which of these are useful to you, and use them.* When someone
+ asks for your public key, tell them to run the above command instead of
+ mailing them your key or sending them a keyserver URL.
+
+* Consider using the pka-related verify-options.
+
+* Look into embracing DNSSEC. With a signed root, there's a good trust-path
+ vector here. Who knows, maybe some day GPG will be dnssec-aware so it will
+ give more credit to a secure DNS transaction. Without a signed root, there
+ are still ways to have those who care about security use it, through
+ services such as [ISC's DLV registry](http://dlv.isc.org).
+
+* On DNSSEC: At present, GPG cannot see the difference between an insecure
+ response (one from an unsigned zone) and a correctly validated one from a
+ signed zone. (In a signed zone, an unsigned or malformed will simply get a
+ SERVFAIL dns response). Look into sponsoring development of GPG to make it
+ as an application more aware of this.
+
+## A better way to generate records
+
+In reading over a lot of these commands, I've come across a few problems with
+the tools involved. They either require you to assemble large records by
+hand, or manipulate huge files.
+
+DNS has also come a long way since these tools were written, and RFCs have
+solidified that have determined the "presentation format" (i.e. the "master
+file format") of what CERT records should look like.
+
+On top of everything, the make-dns-cert tool is not built by default, and is
+not present in most binary distributions (RPM's, deb packages, FreeBSD's
+ports).
+
+Thus, I took it upon myself to rewrite make-dns-cert as a shell script.
+
+### Advantages
+
+* Extracts your key for you (takes a keyid as the argument).
+* Formats all three record types for you, you can pipe it right into your zone
+ file.
+* Takes email address as an argument, generates record label.
+* No compiling needed.
+* Should work with most systems. Requires openssl and sed, a few other
+ standard utilities.
+* Generates base64-ified CERT records, split into easy, manageable pieces.
+* Generates DNS-friendly comments, so repeating tasks are easy to reference.
+* (Eventually) available as a tarball, or as a paste-and-go script.
+* Arguments are in logical DNS record order `emailaddress keyid [url]`.
+* Will generate an IPGP CERT record without a URI (this is legal per RFC4398).
+
+You can see sample output
+[here](http://www.gushi.org/make-dns-cert/sample-output.txt), and you can view
+the script itself
+[here](http://www.gushi.org/make-dns-cert/make-dns-cert.sh.txt). Depending on
+your MIME settings, you can probably get a download link if you go
+[here](http://www.gushi.org/make-dns-cert/make-dns-cert.sh). If you see the
+script rather than getting a download prompt, you can just save-as.
+
+README, Changelog, TODO coming soon.
+
+## Other notes
+
+I'm not 100 percent sure (mainly because I haven't tried), but with IPGP cert,
+and PKA, I believe I could in theory point at a keyserver directly, for
+example, specify a uri of
+[http://pgp.mit.edu:11371/pks/lookup?op=get&amp;search=0xB0307039309C17C5](http://pgp.mit.edu:11371/pks/lookup?op=get&amp;search=0xB0307039309C17C5).
+I'm a bit dubious about the question marks and equals-signs, or if I might
+have to uri-encode things. It's something to be tried.
+
+I'm trying to convince the GPG people that this would be much better adopted
+if the make-dns-cert tool was built/included by default, or if its function
+were included in gpg rather than a third-party tool. This is analagous as to
+how dnssec-keygen is used to generate SSHFP DNS records.
+
+It doesn't do any actual cryptography, just some binary conversion, so in
+theory it could be rewritten in pure-perl, so there's nothing to compile.
+
+I've made the argument to the GPG developers that if multiple CERT records are
+available, all should be tried if one fails. So far, if multiple exist, only
+the first received is parsed, and of course, DNS round-robins the answers by
+default.
+
+It took me quite a lot of trial and error to realize that there's a difference
+between "modern" RSA keys, like this:
+
+ %gpg --list-keys --fingerprint gushi@prime.gushi.org
+ pub 2048R/CF45887D 2009-10-29
+ Key fingerprint = FCB0 485E 050D DDFA 83C6 76E3 E722 3C05 CF45 887D
+ uid Gushi Test &lt;gushi@prime.gushi.org&gt;
+ sub 2048R/C9761244 2009-10-29
+
+and ancient RSA keys like this pgp2.6.2 monster:
+
+ %gpg --list-keys --fingerprint danm@prime.gushi.org
+ pub 1024R/309C17C5 1997-05-08
+ Key fingerprint = 04 4B 1A 2E C4 62 95 73 73 A4 EA D0 08 A4 45 76
+ uid Daniel P. Mahoney &lt;danm@prime.gushi.org&gt;
+
+Note the lack of a subkey there. Note the weird fingerprint. I have not been
+able to get this key to properly export with gpg. If someone knows the Deep
+Magic, let me know.
+
+## References
+
+### Blog posts and list threads
+
+While researching this I came across little more than a few blog posts, and a
+few short discussions on the gpg-devel mailing list.
+
+* [A blog entry](http://www.df7cb.de/blog/2007/openpgp-dns.html) that seems to
+ have things mostly right.
+
+* [GPG Mailing List Discussion](http://lists.gnupg.org/pipermail/gnupg-users/2006-April/028314.html)
+ which seems to date towhen these features were first added.
+
+* [My own thread](http://www.mail-archive.com/gnupg-users@gnupg.org/msg12336.html)
+ on the gnupg-users mailing list that led upto this doc.
+
+* [A slideshow of a talk given on PKA](ftp://ftp.g10code.com/people/werner/talks/pka-intro.ps.gz)
+ (really the only doc I couldfind with regard to PKA). Note that this is a
+ postscript doc, for reasons I cannot fathom.
+
+### RFCs
+
+* [RFC 3597](http://www.faqs.org/rfcs/rfc3597.html) defines the odd format of
+ the records that make-dns-cert generates, if itconfuses you.
+
+* [RFC 2538](http://www.faqs.org/rfcs/rfc2538.html), which was superseded by
+ [RFC4398](http://www.faqs.org/rfcs/rfc4398.html), defines the format for a
+ CERT record.
+
+## Todo
+
+* At least one GPG enthusiast has suggested to me that any tools I write to
+ handle keys should simply be able to insert themusing nsupdate. I don't
+ disagree, but there's a complicated metric there as some of these require
+ manipulation of a site'smain zone, or at the very least, many subzones. In
+ doing this I'd also like to find out a bit about how to do nsupdate
+ withsig(0) and KEY records, which with the right policies would mean I could
+ do this without touching named.conf. That may be the subject of a whole
+ other howto.
+
+* (Done) I need to get the shell script cleaned up a bit more, and generate
+ proper docs, and start tracking it with version control.
+
+* I should probably get the gumption up to formally license all this stuff.
+ For right now, I declare it under the
+ [ISCLicense](http://en.wikipedia.org/wiki/ISC_license).
+
+* I'd like to track down the full list of supported URI types for PKA/IPGP
+ CERT records. There doesn't seem to be a defined standard for it.
+
+## Epilogue
+
+### About the author
+
+Dan Mahoney is a Systems Admin in the Bay Area, California. In his spare time
+he enjoys thinking for those brief fleeting moments what he would do if he had
+more free time. Keyid 624BB249, or email address danm@prime.gushi.org.
+
+### About this Document
+
+This document was written in [gnu nano](http://nano-editor.org), and HTML was
+generated using [Markdown](http://daringfireball.net/projects/markdown).
+
+Markdown rocks.
+
+Originally published on my livejournal at
+[http://gushi.livejournal.com/524199.html](http://gushi.livejournal.com/524199.html),
+its main home is at
+[http://www.gushi.org/make-dns-cert/HOWTO.html](http://www.gushi.org/make-dns-cert/HOWTO.html),
+which is where later versions will be published.
+
+Free to use, comments to the above email address are welcome.
diff --git a/docs/DETAILS b/docs/DETAILS
new file mode 100644
index 0000000..d5c5cea
--- /dev/null
+++ b/docs/DETAILS
@@ -0,0 +1,1225 @@
+# doc/DETAILS -*- org -*-
+#+TITLE: GnuPG Details
+# Globally disable superscripts and subscripts:
+#+OPTIONS: ^:{}
+#
+
+# Note: This file uses org-mode; it should be easy to read as plain
+# text but be aware of some markup peculiarities: Verbatim code is
+# enclosed in #+begin-example, #+end-example blocks or marked by a
+# colon as the first non-white-space character, words bracketed with
+# equal signs indicate a monospace font, and the usual /italics/,
+# *bold*, and _underline_ conventions are recognized.
+
+This is the DETAILS file for GnuPG which specifies some internals and
+parts of the external API for GPG and GPGSM.
+
+* Format of the colon listings
+ The format is a based on colon separated record, each recods starts
+ with a tag string and extends to the end of the line. Here is an
+ example:
+#+begin_example
+$ gpg --with-colons --list-keys \
+ --with-fingerprint --with-fingerprint wk@gnupg.org
+pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
+fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:
+uid:f::::::::Werner Koch <wk@g10code.com>:
+uid:f::::::::Werner Koch <wk@gnupg.org>:
+sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e:
+fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1:
+sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc:
+fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4:
+#+end_example
+
+The double =--with-fingerprint= prints the fingerprint for the subkeys
+too. Old versions of gpg used a lighly different format and required
+the use of the option =--fixed-list-mode= to conform to format
+described here.
+
+** Description of the fields
+*** Field 1 - Type of record
+
+ - pub :: Public key
+ - crt :: X.509 certificate
+ - crs :: X.509 certificate and private key available
+ - sub :: Subkey (secondary key)
+ - sec :: Secret key
+ - ssb :: Secret subkey (secondary key)
+ - uid :: User id (only field 10 is used).
+ - uat :: User attribute (same as user id except for field 10).
+ - sig :: Signature
+ - rev :: Revocation signature
+ - fpr :: Fingerprint (fingerprint is in field 10)
+ - pkd :: Public key data [*]
+ - grp :: Keygrip
+ - rvk :: Revocation key
+ - tru :: Trust database information [*]
+ - spk :: Signature subpacket [*]
+ - cfg :: Configuration data [*]
+
+ Records marked with an asterisk are described at [[*Special%20field%20formats][*Special fields]].
+
+*** Field 2 - Validity
+
+ This is a letter describing the computed validity of a key.
+ Currently this is a single letter, but be prepared that additional
+ information may follow in some future versions. Note that GnuPG <
+ 2.1 does not set this field for secret key listings.
+
+ - o :: Unknown (this key is new to the system)
+ - i :: The key is invalid (e.g. due to a missing self-signature)
+ - d :: The key has been disabled
+ (deprecated - use the 'D' in field 12 instead)
+ - r :: The key has been revoked
+ - e :: The key has expired
+ - - :: Unknown validity (i.e. no value assigned)
+ - q :: Undefined validity. '-' and 'q' may safely be treated as
+ the same value for most purposes
+ - n :: The key is not valid
+ - m :: The key is marginal valid.
+ - f :: The key is fully valid
+ - u :: The key is ultimately valid. This often means that the
+ secret key is available, but any key may be marked as
+ ultimately valid.
+ - w :: The key has a well known private part.
+ - s :: The key has special validity. This means that it might be
+ self-signed and expected to be used in the STEED sytem.
+
+ If the validity information is given for a UID or UAT record, it
+ describes the validity calculated based on this user ID. If given
+ for a key record it describes the validity taken from the best
+ rated user ID.
+
+ For X.509 certificates a 'u' is used for a trusted root
+ certificate (i.e. for the trust anchor) and an 'f' for all other
+ valid certificates.
+
+*** Field 3 - Key length
+
+ The length of key in bits.
+
+*** Field 4 - Public key algorithm
+
+ The values here are those from the OpenPGP specs or if they are
+ greather than 255 the algorithm ids as used by Libgcrypt.
+
+*** Field 5 - KeyID
+
+ This is the 64 bit keyid as specified by OpenPGP and the last 64
+ bit of the SHA-1 fingerprint of an X.509 certifciate.
+
+*** Field 6 - Creation date
+
+ The creation date of the key is given in UTC. For UID and UAT
+ records, this is used for the self-signature date. Note that the
+ date is usally printed in seconds since epoch, however, we are
+ migrating to an ISO 8601 format (e.g. "19660205T091500"). This is
+ currently only relevant for X.509. A simple way to detect the new
+ format is to scan for the 'T'. Note that old versions of gpg
+ without using the =--fixed-list-mode= option used a "yyyy-mm-tt"
+ format.
+
+*** Field 7 - Expiration date
+
+ Key or UID/UAT expiration date or empty if it does not expire.
+
+*** Field 8 - Certificate S/N, UID hash, trust signature info
+
+ Used for serial number in crt records. For UID and UAT records,
+ this is a hash of the user ID contents used to represent that
+ exact user ID. For trust signatures, this is the trust depth
+ seperated by the trust value by a space.
+
+*** Field 9 - Ownertrust
+
+ This is only used on primary keys. This is a single letter, but
+ be prepared that additional information may follow in future
+ versions. For trust signatures with a regular expression, this is
+ the regular expression value, quoted as in field 10.
+
+*** Field 10 - User-ID
+ The value is quoted like a C string to avoid control characters
+ (the colon is quoted =\x3a=). For a "pub" record this field is
+ not used on --fixed-list-mode. A UAT record puts the attribute
+ subpacket count here, a space, and then the total attribute
+ subpacket size. In gpgsm the issuer name comes here. A FPR
+ record stores the fingerprint here. The fingerprint of a
+ revocation key is stored here.
+*** Field 11 - Signature class
+
+ Signature class as per RFC-4880. This is a 2 digit hexnumber
+ followed by either the letter 'x' for an exportable signature or
+ the letter 'l' for a local-only signature. The class byte of an
+ revocation key is also given here, 'x' and 'l' is used the same
+ way. This field if not used for X.509.
+
+*** Field 12 - Key capabilities
+
+ The defined capabilities are:
+
+ - e :: Encrypt
+ - s :: Sign
+ - c :: Certify
+ - a :: Authentication
+ - ? :: Unknown capability
+
+ A key may have any combination of them in any order. In addition
+ to these letters, the primary key has uppercase versions of the
+ letters to denote the _usable_ capabilities of the entire key, and
+ a potential letter 'D' to indicate a disabled key.
+
+*** Field 13 - Issuer certificate fingerprint or other info
+
+ Used in FPR records for S/MIME keys to store the fingerprint of
+ the issuer certificate. This is useful to build the certificate
+ path based on certificates stored in the local key database it is
+ only filled if the issuer certificate is available. The root has
+ been reached if this is the same string as the fingerprint. The
+ advantage of using this value is that it is guaranteed to have
+ been been build by the same lookup algorithm as gpgsm uses.
+
+ For "uid" records this field lists the preferences in the same way
+ gpg's --edit-key menu does.
+
+ For "sig" records, this is the fingerprint of the key that issued
+ the signature. Note that this is only filled in if the signature
+ verified correctly. Note also that for various technical reasons,
+ this fingerprint is only available if --no-sig-cache is used.
+
+*** Field 14 - Flag field
+
+ Flag field used in the --edit menu output
+
+*** Field 15 - S/N of a token
+
+ Used in sec/sbb to print the serial number of a token (internal
+ protect mode 1002) or a '#' if that key is a simple stub (internal
+ protect mode 1001)
+
+*** Field 16 - Hash algorithm
+
+ For sig records, this is the used hash algorithm. For example:
+ 2 = SHA-1, 8 = SHA-256.
+
+** Special fields
+
+*** PKD - Public key data
+
+ If field 1 has the tag "pkd", a listing looks like this:
+#+begin_example
+pkd:0:1024:B665B1435F4C2 .... FF26ABB:
+ ! ! !-- the value
+ ! !------ for information number of bits in the value
+ !--------- index (eg. DSA goes from 0 to 3: p,q,g,y)
+#+end_example
+
+*** TRU - Trust database information
+ Example for a "tru" trust base record:
+#+begin_example
+ tru:o:0:1166697654:1:3:1:5
+#+end_example
+
+ - Field 2 :: Reason for staleness of trust. If this field is
+ empty, then the trustdb is not stale. This field may
+ have multiple flags in it:
+
+ - o :: Trustdb is old
+ - t :: Trustdb was built with a different trust model
+ than the one we are using now.
+
+ - Field 3 :: Trust model
+
+ - 0 :: Classic trust model, as used in PGP 2.x.
+ - 1 :: PGP trust model, as used in PGP 6 and later.
+ This is the same as the classic trust model,
+ except for the addition of trust signatures.
+
+ GnuPG before version 1.4 used the classic trust model
+ by default. GnuPG 1.4 and later uses the PGP trust
+ model by default.
+
+ - Field 4 :: Date trustdb was created in seconds since Epoch.
+ - Field 5 :: Date trustdb will expire in seconds since Epoch.
+ - Field 6 :: Number of marginally trusted users to introduce a new
+ key signer (gpg's option --marginals-needed).
+ - Field 7 :: Number of completely trusted users to introduce a new
+ key signer. (gpg's option --completes-needed)
+
+ - Field 8 :: Maximum depth of a certification chain. (gpg's option
+ --max-cert-depth)
+
+*** SPK - Signature subpacket records
+
+ - Field 2 :: Subpacket number as per RFC-4880 and later.
+ - Field 3 :: Flags in hex. Currently the only two bits assigned
+ are 1, to indicate that the subpacket came from the
+ hashed part of the signature, and 2, to indicate the
+ subpacket was marked critical.
+ - Field 4 :: Length of the subpacket. Note that this is the
+ length of the subpacket, and not the length of field
+ 5 below. Due to the need for %-encoding, the length
+ of field 5 may be up to 3x this value.
+ - Field 5 :: The subpacket data. Printable ASCII is shown as
+ ASCII, but other values are rendered as %XX where XX
+ is the hex value for the byte.
+
+*** CFG - Configuration data
+
+ --list-config outputs information about the GnuPG configuration
+ for the benefit of frontends or other programs that call GnuPG.
+ There are several list-config items, all colon delimited like the
+ rest of the --with-colons output. The first field is always "cfg"
+ to indicate configuration information. The second field is one of
+ (with examples):
+
+ - version :: The third field contains the version of GnuPG.
+
+ : cfg:version:1.3.5
+
+ - pubkey :: The third field contains the public key algorithms
+ this version of GnuPG supports, separated by
+ semicolons. The algorithm numbers are as specified in
+ RFC-4880. Note that in contrast to the --status-fd
+ interface these are _not_ the Libgcrypt identifiers.
+
+ : cfg:pubkey:1;2;3;16;17
+
+ - cipher :: The third field contains the symmetric ciphers this
+ version of GnuPG supports, separated by semicolons.
+ The cipher numbers are as specified in RFC-4880.
+
+ : cfg:cipher:2;3;4;7;8;9;10
+
+ - digest :: The third field contains the digest (hash) algorithms
+ this version of GnuPG supports, separated by
+ semicolons. The digest numbers are as specified in
+ RFC-4880.
+
+ : cfg:digest:1;2;3;8;9;10
+
+ - compress :: The third field contains the compression algorithms
+ this version of GnuPG supports, separated by
+ semicolons. The algorithm numbers are as specified
+ in RFC-4880.
+
+ : cfg:compress:0;1;2;3
+
+ - group :: The third field contains the name of the group, and the
+ fourth field contains the values that the group expands
+ to, separated by semicolons.
+
+ For example, a group of:
+ : group mynames = paige 0x12345678 joe patti
+ would result in:
+ : cfg:group:mynames:patti;joe;0x12345678;paige
+
+
+* Format of the --status-fd output
+
+ Every line is prefixed with "[GNUPG:] ", followed by a keyword with
+ the type of the status line and some arguments depending on the type
+ (maybe none); an application should always be prepared to see more
+ arguments in future versions.
+
+** General status codes
+*** NEWSIG
+ May be issued right before a signature verification starts. This
+ is useful to define a context for parsing ERROR status messages.
+ No arguments are currently defined.
+
+*** GOODSIG <long_keyid_or_fpr> <username>
+ The signature with the keyid is good. For each signature only one
+ of the codes GOODSIG, BADSIG, EXPSIG, EXPKEYSIG, REVKEYSIG or
+ ERRSIG will be emitted. In the past they were used as a marker
+ for a new signature; new code should use the NEWSIG status
+ instead. The username is the primary one encoded in UTF-8 and %XX
+ escaped. The fingerprint may be used instead of the long keyid if
+ it is available. This is the case with CMS and might eventually
+ also be available for OpenPGP.
+
+*** EXPSIG <long_keyid_or_fpr> <username>
+ The signature with the keyid is good, but the signature is
+ expired. The username is the primary one encoded in UTF-8 and %XX
+ escaped. The fingerprint may be used instead of the long keyid if
+ it is available. This is the case with CMS and might eventually
+ also be available for OpenPGP.
+
+*** EXPKEYSIG <long_keyid_or_fpr> <username>
+ The signature with the keyid is good, but the signature was made
+ by an expired key. The username is the primary one encoded in
+ UTF-8 and %XX escaped. The fingerprint may be used instead of the
+ long keyid if it is available. This is the case with CMS and
+ might eventually also be available for OpenPGP.
+
+*** REVKEYSIG <long_keyid_or_fpr> <username>
+ The signature with the keyid is good, but the signature was made
+ by a revoked key. The username is the primary one encoded in UTF-8
+ and %XX escaped. The fingerprint may be used instead of the long
+ keyid if it is available. This is the case with CMS and might
+ eventually also beñ available for OpenPGP.
+
+*** BADSIG <long_keyid_or_fpr> <username>
+ The signature with the keyid has not been verified okay. The
+ username is the primary one encoded in UTF-8 and %XX escaped. The
+ fingerprint may be used instead of the long keyid if it is
+ available. This is the case with CMS and might eventually also be
+ available for OpenPGP.
+
+*** ERRSIG <keyid> <pkalgo> <hashalgo> <sig_class> <time> <rc>
+ It was not possible to check the signature. This may be caused by
+ a missing public key or an unsupported algorithm. A RC of 4
+ indicates unknown algorithm, a 9 indicates a missing public
+ key. The other fields give more information about this signature.
+ sig_class is a 2 byte hex-value. The fingerprint may be used
+ instead of the keyid if it is available. This is the case with
+ gpgsm and might eventually also be available for OpenPGP.
+
+ Note, that TIME may either be the number of seconds since Epoch or
+ the letter 'T'.
+ an ISO 8601 string. The latter can be detected by the presence of
+
+*** VALIDSIG <args>
+
+ The args are:
+
+ - <fingerprint_in_hex>
+ - <sig_creation_date>
+ - <sig-timestamp>
+ - <expire-timestamp>
+ - <sig-version>
+ - <reserved>
+ - <pubkey-algo>
+ - <hash-algo>
+ - <sig-class>
+ - [ <primary-key-fpr> ]
+
+ This status indicates that the signature is good. This is the same
+ as GOODSIG but has the fingerprint as the argument. Both status
+ lines are emitted for a good signature. All arguments here are on
+ one long line. sig-timestamp is the signature creation time in
+ seconds after the epoch. expire-timestamp is the signature
+ expiration time in seconds after the epoch (zero means "does not
+ expire"). sig-version, pubkey-algo, hash-algo, and sig-class (a
+ 2-byte hex value) are all straight from the signature packet.
+ PRIMARY-KEY-FPR is the fingerprint of the primary key or identical
+ to the first argument. This is useful to get back to the primary
+ key without running gpg again for this purpose.
+
+ The primary-key-fpr parameter is used for OpenPGP and not
+ class is not defined for CMS and currently set to 0 and 00.
+ available for CMS signatures. The sig-version as well as the sig
+
+ Note, that *-TIMESTAMP may either be a number of seconds since
+ Epoch or an ISO 8601 string which can be detected by the presence
+ of the letter 'T'.
+
+*** SIG_ID <radix64_string> <sig_creation_date> <sig-timestamp>
+ This is emitted only for signatures of class 0 or 1 which have
+ been verified okay. The string is a signature id and may be used
+ in applications to detect replay attacks of signed messages. Note
+ that only DLP algorithms give unique ids - others may yield
+ duplicated ones when they have been created in the same second.
+
+ Note, that SIG-TIMESTAMP may either be a number of seconds since
+ Epoch or an ISO 8601 string which can be detected by the presence
+ of the letter 'T'.
+
+*** ENC_TO <long_keyid> <keytype> <keylength>
+ The message is encrypted to this LONG_KEYID. KEYTYPE is the
+ numerical value of the public key algorithm or 0 if it is not
+ known, KEYLENGTH is the length of the key or 0 if it is not known
+ (which is currently always the case). Gpg prints this line
+ always; Gpgsm only if it knows the certificate.
+
+*** BEGIN_DECRYPTION
+ Mark the start of the actual decryption process. This is also
+ emitted when in --list-only mode.
+*** END_DECRYPTION
+ Mark the end of the actual decryption process. This are also
+ emitted when in --list-only mode.
+*** DECRYPTION_INFO <mdc_method> <sym_algo>
+ Print information about the symmetric encryption algorithm and the
+ MDC method. This will be emitted even if the decryption fails.
+
+*** DECRYPTION_FAILED
+ The symmetric decryption failed - one reason could be a wrong
+ passphrase for a symmetrical encrypted message.
+
+*** DECRYPTION_OKAY
+ The decryption process succeeded. This means, that either the
+ correct secret key has been used or the correct passphrase for a
+ conventional encrypted message was given. The program itself may
+ return an errorcode because it may not be possible to verify a
+ signature for some reasons.
+
+*** SESSION_KEY <algo>:<hexdigits>
+ The session key used to decrypt the message. This message will
+ only be emitted when the special option --show-session-key is
+ used. The format is suitable to be passed to the option
+ --override-session-key
+
+*** BEGIN_ENCRYPTION <mdc_method> <sym_algo>
+ Mark the start of the actual encryption process.
+
+*** END_ENCRYPTION
+ Mark the end of the actual encryption process.
+
+*** FILE_START <what> <filename>
+ Start processing a file <filename>. <what> indicates the performed
+ operation:
+ - 1 :: verify
+ - 2 :: encrypt
+ - 3 :: decrypt
+
+*** FILE_DONE
+ Marks the end of a file processing which has been started
+ by FILE_START.
+
+*** BEGIN_SIGNING
+ Mark the start of the actual signing process. This may be used as
+ an indication that all requested secret keys are ready for use.
+
+*** ALREADY_SIGNED <long-keyid>
+ Warning: This is experimental and might be removed at any time.
+
+*** SIG_CREATED <type> <pk_algo> <hash_algo> <class> <timestamp> <keyfpr>
+ A signature has been created using these parameters.
+ Values for type <type> are:
+ - D :: detached
+ - C :: cleartext
+ - S :: standard
+ (only the first character should be checked)
+
+ <class> are 2 hex digits with the OpenPGP signature class.
+
+ Note, that TIMESTAMP may either be a number of seconds since Epoch
+ or an ISO 8601 string which can be detected by the presence of the
+ letter 'T'.
+
+*** NOTATION_
+ There are actually two related status codes to convey notation
+ data:
+
+ - NOTATION_NAME <name>
+ - NOTATION_DATA <string>
+
+ <name> and <string> are %XX escaped; the data may be split among
+ several NOTATION_DATA lines.
+
+*** POLICY_URL <string>
+ Note that URL in <string> is %XX escaped.
+
+*** PLAINTEXT <format> <timestamp> <filename>
+ This indicates the format of the plaintext that is about to be
+ written. The format is a 1 byte hex code that shows the format of
+ the plaintext: 62 ('b') is binary data, 74 ('t') is text data with
+ no character set specified, and 75 ('u') is text data encoded in
+ the UTF-8 character set. The timestamp is in seconds since the
+ epoch. If a filename is available it gets printed as the third
+ argument, percent-escaped as usual.
+
+*** PLAINTEXT_LENGTH <length>
+ This indicates the length of the plaintext that is about to be
+ written. Note that if the plaintext packet has partial length
+ encoding it is not possible to know the length ahead of time. In
+ that case, this status tag does not appear.
+
+*** ATTRIBUTE <arguments>
+ The list or argemnts are:
+ - <fpr>
+ - <octets>
+ - <type>
+ - <index>
+ - <count>
+ - <timestamp>
+ - <expiredate>
+ - <flags>
+
+ This is one long line issued for each attribute subpacket when an
+ attribute packet is seen during key listing. <fpr> is the
+ fingerprint of the key. <octets> is the length of the attribute
+ subpacket. <type> is the attribute type (e.g. 1 for an image).
+ <index> and <count> indicate that this is the N-th indexed
+ subpacket of count total subpackets in this attribute packet.
+ <timestamp> and <expiredate> are from the self-signature on the
+ attribute packet. If the attribute packet does not have a valid
+ self-signature, then the timestamp is 0. <flags> are a bitwise OR
+ of:
+ - 0x01 :: this attribute packet is a primary uid
+ - 0x02 :: this attribute packet is revoked
+ - 0x04 :: this attribute packet is expired
+
+*** SIG_SUBPACKET <type> <flags> <len> <data>
+ This indicates that a signature subpacket was seen. The format is
+ the same as the "spk" record above.
+
+** Key related
+*** INV_RECP, INV_SGNR
+ The two similar status codes:
+
+ - INV_RECP <reason> <requested_recipient>
+ - INV_SGNR <reason> <requested_sender>
+
+ are issued for each unusable recipient/sender. The reasons codes
+ currently in use are:
+
+ - 0 :: No specific reason given
+ - 1 :: Not Found
+ - 2 :: Ambigious 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
+
+ Note that for historical reasons the INV_RECP status is also used
+ for gpgsm's SIGNER command where it relates to signer's of course.
+ Newer GnuPG versions are using INV_SGNR; applications should
+ ignore the INV_RECP during the sender's command processing once
+ they have seen an INV_SGNR. Different codes are used so that they
+ can be distinguish while doing an encrypt+sign operation.
+*** NO_RECP <reserved>
+ Issued if no recipients are usable.
+
+*** NO_SGNR <reserved>
+ Issued if no senders are usable.
+
+*** KEYEXPIRED <expire-timestamp>
+ The key has expired. expire-timestamp is the expiration time in
+ seconds since Epoch. This status line is not very useful because
+ it will also be emitted for expired subkeys even if this subkey is
+ not used. To check whether a key used to sign a message has
+ expired, the EXPKEYSIG status line is to be used.
+
+ Note, that the TIMESTAMP may either be a number of seconds since
+ Epoch or an ISO 8601 string which can be detected by the presence
+ of the letter 'T'.
+
+*** KEYREVOKED
+ The used key has been revoked by its owner. No arguments yet.
+
+*** NO_PUBKEY <long keyid>
+ The public key is not available
+
+*** NO_SECKEY <long keyid>
+ The secret key is not available
+
+*** KEY_CREATED <type> <fingerprint> [<handle>]
+ A key has been created. Values for <type> are:
+ - B :: primary and subkey
+ - P :: primary
+ - S :: subkey
+ The fingerprint is one of the primary key for type B and P and the
+ one of the subkey for S. Handle is an arbitrary non-whitespace
+ string used to match key parameters from batch key creation run.
+
+*** KEY_NOT_CREATED [<handle>]
+ The key from batch run has not been created due to errors.
+
+*** TRUST_
+ These are several similar status codes:
+
+ - TRUST_UNDEFINED <error_token>
+ - TRUST_NEVER <error_token>
+ - TRUST_MARGINAL [0 [<validation_model>]]
+ - TRUST_FULLY [0 [<validation_model>]]
+ - TRUST_ULTIMATE [0 [<validation_model>]]
+
+ For good signatures one of these status lines are emitted to
+ indicate the validity of the key used to create the signature.
+ The error token values are currently only emitted by gpgsm.
+
+ VALIDATION_MODEL describes the algorithm used to check the
+ validity of the key. The defaults are the standard Web of Trust
+ model for gpg and the the standard X.509 model for gpgsm. The
+ defined values are
+
+ - pgp :: The standard PGP WoT.
+ - shell :: The standard X.509 model.
+ - chain :: The chain model.
+ - steed :: The STEED model.
+
+ Note that the term =TRUST_= in the status names is used for
+ historic reasons; we now speak of validity.
+
+*** PKA_TRUST_
+ This is is one:
+
+ - PKA_TRUST_GOOD <mailbox>
+ - PKA_TRUST_BAD <mailbox>
+
+ Depending on the outcome of the PKA check one of the above status
+ codes is emitted in addition to a =TRUST_*= status.
+
+** Remote control
+*** GET_BOOL, GET_LINE, GET_HIDDEN, GOT_IT
+
+ These status line are used with --command-fd for interactive
+ control of the process.
+
+*** USERID_HINT <long main keyid> <string>
+ Give a hint about the user ID for a certain keyID.
+
+*** NEED_PASSPHRASE <long keyid> <long main keyid> <keytype> <keylength>
+ Issued whenever a passphrase is needed. KEYTYPE is the numerical
+ value of the public key algorithm or 0 if this is not applicable,
+ KEYLENGTH is the length of the key or 0 if it is not known (this
+ is currently always the case).
+
+*** NEED_PASSPHRASE_SYM <cipher_algo> <s2k_mode> <s2k_hash>
+ Issued whenever a passphrase for symmetric encryption is needed.
+
+*** NEED_PASSPHRASE_PIN <card_type> <chvno> [<serialno>]
+ Issued whenever a PIN is requested to unlock a card.
+
+*** MISSING_PASSPHRASE
+ No passphrase was supplied. An application which encounters this
+ message may want to stop parsing immediately because the next
+ message will probably be a BAD_PASSPHRASE. However, if the
+ application is a wrapper around the key edit menu functionality it
+ might not make sense to stop parsing but simply ignoring the
+ following BAD_PASSPHRASE.
+
+*** BAD_PASSPHRASE <long keyid>
+ The supplied passphrase was wrong or not given. In the latter
+ case you may have seen a MISSING_PASSPHRASE.
+
+*** GOOD_PASSPHRASE
+ The supplied passphrase was good and the secret key material
+ is therefore usable.
+
+** Import/Export
+*** IMPORT_CHECK <long keyid> <fingerprint> <user ID>
+ This status is emitted in interactive mode right before
+ the "import.okay" prompt.
+
+*** IMPORTED <long keyid> <username>
+ The keyid and name of the signature just imported
+
+*** IMPORT_OK <reason> [<fingerprint>]
+ The key with the primary key's FINGERPRINT has been imported.
+ REASON flags are:
+
+ - 0 :: Not actually changed
+ - 1 :: Entirely new key.
+ - 2 :: New user IDs
+ - 4 :: New signatures
+ - 8 :: New subkeys
+ - 16 :: Contains private key.
+
+ The flags may be ORed.
+
+*** IMPORT_PROBLEM <reason> [<fingerprint>]
+ Issued for each import failure. Reason codes are:
+
+ - 0 :: No specific reason given.
+ - 1 :: Invalid Certificate.
+ - 2 :: Issuer Certificate missing.
+ - 3 :: Certificate Chain too long.
+ - 4 :: Error storing certificate.
+
+*** IMPORT_RES <args>
+ Final statistics on import process (this is one long line). The
+ args are a list of unsigned numbers separated by white space:
+
+ - <count>
+ - <no_user_id>
+ - <imported>
+ - <imported_rsa>
+ - <unchanged>
+ - <n_uids>
+ - <n_subk>
+ - <n_sigs>
+ - <n_revoc>
+ - <sec_read>
+ - <sec_imported>
+ - <sec_dups>
+ - <skipped_new_keys>
+ - <not_imported>
+
+** Smartcard related
+*** CARDCTRL <what> [<serialno>]
+ This is used to control smartcard operations. Defined values for
+ WHAT are:
+
+ - 1 :: Request insertion of a card. Serialnumber may be given
+ to request a specific card. Used by gpg 1.4 w/o
+ scdaemon
+ - 2 :: Request removal of a card. Used by gpg 1.4 w/o scdaemon.
+ - 3 :: Card with serialnumber detected
+ - 4 :: No card available
+ - 5 :: No card reader available
+ - 6 :: No card support available
+
+*** SC_OP_FAILURE [<code>]
+ An operation on a smartcard definitely failed. Currently there is
+ no indication of the actual error code, but application should be
+ prepared to later accept more arguments. Defined values for
+ <code> are:
+
+ - 0 :: unspecified error (identically to a missing CODE)
+ - 1 :: canceled
+ - 2 :: bad PIN
+
+*** SC_OP_SUCCESS
+ A smart card operaion succeeded. This status is only printed for
+ certain operation and is mostly useful to check whether a PIN
+ change really worked.
+
+** Miscellaneous status codes
+*** NODATA <what>
+ No data has been found. Codes for WHAT are:
+
+ - 1 :: No armored data.
+ - 2 :: Expected a packet but did not found one.
+ - 3 :: Invalid packet found, this may indicate a non OpenPGP
+ message.
+ - 4 :: Signature expected but not found
+
+ You may see more than one of these status lines.
+
+*** UNEXPECTED <what>
+ Unexpected data has been encountered. Codes for WHAT are:
+ - 0 :: Not further specified
+
+*** TRUNCATED <maxno>
+ The output was truncated to MAXNO items. This status code is
+ issued for certain external requests.
+
+*** ERROR <error location> <error code> [<more>]
+ This is a generic error status message, it might be followed by
+ error location specific data. <error code> and <error_location>
+ should not contain spaces. The error code is a either a string
+ commencing with a letter or such a string prefixed with a
+ numerical error code and an underscore; e.g.: "151011327_EOF".
+
+*** SUCCESS [<location>]
+ Postive confirimation that an operation succeeded. <location> is
+ optional but if given should not contain spaces. Used only with a
+ few commands.
+
+*** BADARMOR
+ The ASCII armor is corrupted. No arguments yet.
+
+*** DELETE_PROBLEM <reason_code>
+ Deleting a key failed. Reason codes are:
+ - 1 :: No such key
+ - 2 :: Must delete secret key first
+ - 3 :: Ambigious specification
+
+*** PROGRESS <what> <char> <cur> <total>
+ Used by the primegen and Public key functions to indicate
+ progress. <char> is the character displayed with no --status-fd
+ enabled, with the linefeed replaced by an 'X'. <cur> is the
+ current amount done and <total> is amount to be done; a <total> of
+ 0 indicates that the total amount is not known. The condition
+ : TOTAL && CUR == TOTAL
+ may be used to detect the end of an operation.
+
+ Well known values for WHAT are:
+
+ - pk_dsa :: DSA key generation
+ - pk_elg :: Elgamal key generation
+ - primegen :: Prime generation
+ - need_entropy :: Waiting for new entropy in the RNG
+ - tick :: Generic tick without any special meaning - useful
+ for letting clients know that the server is still
+ working.
+ - starting_agent :: A gpg-agent was started because it is not
+ running as a daemon.
+ - learncard :: Send by the agent and gpgsm while learing
+ the data of a smartcard.
+ - card_busy :: A smartcard is still working
+
+*** BACKUP_KEY_CREATED <fingerprint> <fname>
+ A backup of a key identified by <fingerprint> has been writte to
+ the file <fname>; <fname> is percent-escaped.
+
+*** MOUNTPOINT <name>
+ <name> is a percent-plus escaped filename describing the
+ mountpoint for the current operation (e.g. used by "g13 --mount").
+ This may either be the specified mountpoint or one randomly
+ choosen by g13.
+
+*** PINENTRY_LAUNCHED <pid>
+ This status line is emitted by gpg to notify a client that a
+ Pinentry has been launched. <pid> is the PID of the Pinentry. It
+ may be used to display a hint to the user but can't be used to
+ synchronize with Pinentry. Note that there is also an Assuan
+ inquiry line with the same name used internally or, if enabled,
+ send to the client instead of this status line. Such an inquiry
+ may be used to sync with Pinentry
+
+** Obsolete status codes
+*** SIGEXPIRED
+ Removed on 2011-02-04. This is deprecated in favor of KEYEXPIRED.
+*** RSA_OR_IDEA
+ Obsolete. This status message used to be emitted for requests to
+ use the IDEA or RSA algorithms. It has been dropped from GnuPG
+ 2.1 after the respective patents expired.
+*** SHM_INFO, SHM_GET, SHM_GET_BOOL, SHM_GET_HIDDEN
+ These were used for the ancient shared memory based co-processing.
+*** BEGIN_STREAM, END_STREAM
+ Used to issued by the experimental pipemode.
+
+
+* Format of the --attribute-fd output
+
+ When --attribute-fd is set, during key listings (--list-keys,
+ --list-secret-keys) GnuPG dumps each attribute packet to the file
+ descriptor specified. --attribute-fd is intended for use with
+ --status-fd as part of the required information is carried on the
+ ATTRIBUTE status tag (see above).
+
+ The contents of the attribute data is specified by RFC 4880. For
+ convenience, here is the Photo ID format, as it is currently the
+ only attribute defined:
+
+ - Byte 0-1 :: The length of the image header. Due to a historical
+ accident (i.e. oops!) back in the NAI PGP days, this
+ is a little-endian number. Currently 16 (0x10 0x00).
+
+ - Byte 2 :: The image header version. Currently 0x01.
+
+ - Byte 3 :: Encoding format. 0x01 == JPEG.
+
+ - Byte 4-15 :: Reserved, and currently unused.
+
+ All other data after this header is raw image (JPEG) data.
+
+
+* Unattended key generation
+
+ Please see the GnuPG manual for a description.
+
+
+* Layout of the TrustDB
+
+ The TrustDB is built from fixed length records, where the first byte
+ describes the record type. All numeric values are stored in network
+ byte order. The length of each record is 40 bytes. The first record
+ of the DB is always of type 1 and this is the only record of this
+ type.
+
+ FIXME: The layout changed, document it here.
+#+begin_example
+ Record type 0:
+ --------------
+ Unused record, can be reused for any purpose.
+
+ Record type 1:
+ --------------
+ Version information for this TrustDB. This is always the first
+ record of the DB and the only one with type 1.
+ 1 byte value 1
+ 3 bytes 'gpg' magic value
+ 1 byte Version of the TrustDB (2)
+ 1 byte marginals needed
+ 1 byte completes needed
+ 1 byte max_cert_depth
+ The three items are used to check whether the cached
+ validity value from the dir record can be used.
+ 1 u32 locked flags [not used]
+ 1 u32 timestamp of trustdb creation
+ 1 u32 timestamp of last modification which may affect the validity
+ of keys in the trustdb. This value is checked against the
+ validity timestamp in the dir records.
+ 1 u32 timestamp of last validation [currently not used]
+ (Used to keep track of the time, when this TrustDB was checked
+ against the pubring)
+ 1 u32 record number of keyhashtable [currently not used]
+ 1 u32 first free record
+ 1 u32 record number of shadow directory hash table [currently not used]
+ It does not make sense to combine this table with the key table
+ because the keyid is not in every case a part of the fingerprint.
+ 1 u32 record number of the trusthashtbale
+
+
+ Record type 2: (directory record)
+ --------------
+ Informations about a public key certificate.
+ These are static values which are never changed without user interaction.
+
+ 1 byte value 2
+ 1 byte reserved
+ 1 u32 LID . (This is simply the record number of this record.)
+ 1 u32 List of key-records (the first one is the primary key)
+ 1 u32 List of uid-records
+ 1 u32 cache record
+ 1 byte ownertrust
+ 1 byte dirflag
+ 1 byte maximum validity of all the user ids
+ 1 u32 time of last validity check.
+ 1 u32 Must check when this time has been reached.
+ (0 = no check required)
+
+
+ Record type 3: (key record)
+ --------------
+ Informations about a primary public key.
+ (This is mainly used to lookup a trust record)
+
+ 1 byte value 3
+ 1 byte reserved
+ 1 u32 LID
+ 1 u32 next - next key record
+ 7 bytes reserved
+ 1 byte keyflags
+ 1 byte pubkey algorithm
+ 1 byte length of the fingerprint (in bytes)
+ 20 bytes fingerprint of the public key
+ (This is the value we use to identify a key)
+
+ Record type 4: (uid record)
+ --------------
+ Informations about a userid
+ We do not store the userid but the hash value of the userid because that
+ is sufficient.
+
+ 1 byte value 4
+ 1 byte reserved
+ 1 u32 LID points to the directory record.
+ 1 u32 next next userid
+ 1 u32 pointer to preference record
+ 1 u32 siglist list of valid signatures
+ 1 byte uidflags
+ 1 byte validity of the key calculated over this user id
+ 20 bytes ripemd160 hash of the username.
+
+
+ Record type 5: (pref record)
+ --------------
+ This record type is not anymore used.
+
+ 1 byte value 5
+ 1 byte reserved
+ 1 u32 LID; points to the directory record (and not to the uid record!).
+ (or 0 for standard preference record)
+ 1 u32 next
+ 30 byte preference data
+
+ Record type 6 (sigrec)
+ -------------
+ Used to keep track of key signatures. Self-signatures are not
+ stored. If a public key is not in the DB, the signature points to
+ a shadow dir record, which in turn has a list of records which
+ might be interested in this key (and the signature record here
+ is one).
+
+ 1 byte value 6
+ 1 byte reserved
+ 1 u32 LID points back to the dir record
+ 1 u32 next next sigrec of this uid or 0 to indicate the
+ last sigrec.
+ 6 times
+ 1 u32 Local_id of signatures dir or shadow dir record
+ 1 byte Flag: Bit 0 = checked: Bit 1 is valid (we have a real
+ directory record for this)
+ 1 = valid is set (but may be revoked)
+
+
+
+ Record type 8: (shadow directory record)
+ --------------
+ This record is used to reserve a LID for a public key. We
+ need this to create the sig records of other keys, even if we
+ do not yet have the public key of the signature.
+ This record (the record number to be more precise) will be reused
+ as the dir record when we import the real public key.
+
+ 1 byte value 8
+ 1 byte reserved
+ 1 u32 LID (This is simply the record number of this record.)
+ 2 u32 keyid
+ 1 byte pubkey algorithm
+ 3 byte reserved
+ 1 u32 hintlist A list of records which have references to
+ this key. This is used for fast access to
+ signature records which are not yet checked.
+ Note, that this is only a hint and the actual records
+ may not anymore hold signature records for that key
+ but that the code cares about this.
+ 18 byte reserved
+
+
+
+ Record Type 10 (hash table)
+ --------------
+ Due to the fact that we use fingerprints to lookup keys, we can
+ implement quick access by some simple hash methods, and avoid
+ the overhead of gdbm. A property of fingerprints is that they can be
+ used directly as hash values. (They can be considered as strong
+ random numbers.)
+ What we use is a dynamic multilevel architecture, which combines
+ hashtables, record lists, and linked lists.
+
+ This record is a hashtable of 256 entries; a special property
+ is that all these records are stored consecutively to make one
+ big table. The hash value is simple the 1st, 2nd, ... byte of
+ the fingerprint (depending on the indirection level).
+
+ When used to hash shadow directory records, a different table is used
+ and indexed by the keyid.
+
+ 1 byte value 10
+ 1 byte reserved
+ n u32 recnum; n depends on the record length:
+ n = (reclen-2)/4 which yields 9 for the current record length
+ of 40 bytes.
+
+ the total number of such record which makes up the table is:
+ m = (256+n-1) / n
+ which is 29 for a record length of 40.
+
+ To look up a key we use the first byte of the fingerprint to get
+ the recnum from this hashtable and look up the addressed record:
+ - If this record is another hashtable, we use 2nd byte
+ to index this hash table and so on.
+ - if this record is a hashlist, we walk all entries
+ until we found one a matching one.
+ - if this record is a key record, we compare the
+ fingerprint and to decide whether it is the requested key;
+
+
+ Record type 11 (hash list)
+ --------------
+ see hash table for an explanation.
+ This is also used for other purposes.
+
+ 1 byte value 11
+ 1 byte reserved
+ 1 u32 next next hash list record
+ n times n = (reclen-5)/5
+ 1 u32 recnum
+
+ For the current record length of 40, n is 7
+
+
+
+ Record type 254 (free record)
+ ---------------
+ All these records form a linked list of unused records.
+ 1 byte value 254
+ 1 byte reserved (0)
+ 1 u32 next_free
+#+end_example
+
+
+* GNU extensions to the S2K algorithm
+
+ S2K mode 101 is used to identify these extensions.
+ After the hash algorithm the 3 bytes "GNU" are used to make
+ clear that these are extensions for GNU, the next bytes gives the
+ GNU protection mode - 1000. Defined modes are:
+ - 1001 :: Do not store the secret part at all.
+ - 1002 :: A stub to access smartcards (not used in 1.2.x)
+
+* Keyserver helper message format
+
+ The keyserver may be contacted by a Unix Domain socket or via TCP.
+
+ The format of a request is:
+#+begin_example
+ command-tag
+ "Content-length:" digits
+ CRLF
+#+end_example
+
+ Where command-tag is
+
+#+begin_example
+ NOOP
+ GET <user-name>
+ PUT
+ DELETE <user-name>
+#+end_example
+
+The format of a response is:
+
+#+begin_example
+ "GNUPG/1.0" status-code status-text
+ "Content-length:" digits
+ CRLF
+#+end_example
+followed by <digits> bytes of data
+
+Status codes are:
+
+ - 1xx :: Informational - Request received, continuing process
+
+ - 2xx :: Success - The action was successfully received, understood,
+ and accepted
+
+ - 4xx :: Client Error - The request contains bad syntax or cannot be
+ fulfilled
+
+ - 5xx :: Server Error - The server failed to fulfill an apparently
+ valid request
+
+
+* Object identifiers
+
+ OIDs below the GnuPG arc:
+
+#+begin_example
+ 1.3.6.1.4.1.11591.2 GnuPG
+ 1.3.6.1.4.1.11591.2.1 notation
+ 1.3.6.1.4.1.11591.2.1.1 pkaAddress
+ 1.3.6.1.4.1.11591.2.2 X.509 extensions
+ 1.3.6.1.4.1.11591.2.2.1 standaloneCertificate
+ 1.3.6.1.4.1.11591.2.2.2 wellKnownPrivateKey
+ 1.3.6.1.4.1.11591.2.12242973 invalid encoded OID
+#+end_example
+
+
+
+* Miscellaneous notes
+
+** v3 fingerprints
+ For packet version 3 we calculate the keyids this way:
+ - RSA :: Low 64 bits of n
+ - ELGAMAL :: Build a v3 pubkey packet (with CTB 0x99) and
+ calculate a RMD160 hash value from it. This is used
+ as the fingerprint and the low 64 bits are the keyid.
+
+** Simplified revocation certificates
+ Revocation certificates consist only of the signature packet;
+ "--import" knows how to handle this. The rationale behind it is to
+ keep them small.
+
+** Documentation on HKP (the http keyserver protocol):
+
+ A minimalistic HTTP server on port 11371 recognizes a GET for
+ /pks/lookup. The standard http URL encoded query parameters are
+ this (always key=value):
+
+ - op=index (like pgp -kv), op=vindex (like pgp -kvv) and op=get (like
+ pgp -kxa)
+
+ - search=<stringlist>. This is a list of words that must occur in the key.
+ The words are delimited with space, points, @ and so on. The delimiters
+ are not searched for and the order of the words doesn't matter (but see
+ next option).
+
+ - exact=on. This switch tells the hkp server to only report exact matching
+ keys back. In this case the order and the "delimiters" are important.
+
+ - fingerprint=on. Also reports the fingerprints when used with 'index' or
+ 'vindex'
+
+ The keyserver also recognizes http-POSTs to /pks/add. Use this to upload
+ keys.
+
+
+ A better way to do this would be a request like:
+
+ /pks/lookup/<gnupg_formatierte_user_id>?op=<operation>
+
+ This can be implemented using Hurd's translator mechanism.
+ However, I think the whole key server stuff has to be re-thought;
+ I have some ideas and probably create a white paper.
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..2d43a93
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS = -E -n
+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) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+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 " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @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."
+
+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/python-gnupg.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-gnupg.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/python-gnupg"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-gnupg"
+ @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."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+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."
diff --git a/docs/NOTES-python-gnupg-3.1-audit.html b/docs/NOTES-python-gnupg-3.1-audit.html
new file mode 100644
index 0000000..fbd6e0d
--- /dev/null
+++ b/docs/NOTES-python-gnupg-3.1-audit.html
@@ -0,0 +1,946 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+<title>python-gnupg audit</title>
+<meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1"/>
+<meta name="title" content="python-gnupg audit"/>
+<meta name="generator" content="Org-mode"/>
+<meta name="generated" content="2013-02-01 Fri"/>
+<meta name="author" content="isis"/>
+<meta name="description" content=""/>
+<meta name="keywords" content=""/>
+<style type="text/css">
+ <!--/*--><![CDATA[/*><!--*/
+ html { font-family: Times, serif; font-size: 12pt; }
+ .title { text-align: center; }
+ .todo { color: red; }
+ .done { color: green; }
+ .tag { background-color: #add8e6; font-weight:normal }
+ .target { }
+ .timestamp { color: #bebebe; }
+ .timestamp-kwd { color: #5f9ea0; }
+ .right {margin-left:auto; margin-right:0px; text-align:right;}
+ .left {margin-left:0px; margin-right:auto; text-align:left;}
+ .center {margin-left:auto; margin-right:auto; text-align:center;}
+ p.verse { margin-left: 3% }
+ pre {
+ border: 1pt solid #AEBDCC;
+ background-color: #F3F5F7;
+ padding: 5pt;
+ font-family: courier, monospace;
+ font-size: 90%;
+ overflow:auto;
+ }
+ table { border-collapse: collapse; }
+ td, th { vertical-align: top; }
+ th.right { text-align:center; }
+ th.left { text-align:center; }
+ th.center { text-align:center; }
+ td.right { text-align:right; }
+ td.left { text-align:left; }
+ td.center { text-align:center; }
+ dt { font-weight: bold; }
+ div.figure { padding: 0.5em; }
+ div.figure p { text-align: center; }
+ div.inlinetask {
+ padding:10px;
+ border:2px solid gray;
+ margin:10px;
+ background: #ffffcc;
+ }
+ textarea { overflow-x: auto; }
+ .linenr { font-size:smaller }
+ .code-highlighted {background-color:#ffff00;}
+ .org-info-js_info-navigation { border-style:none; }
+ #org-info-js_console-label { font-size:10px; font-weight:bold;
+ white-space:nowrap; }
+ .org-info-js_search-highlight {background-color:#ffff00; color:#000000;
+ font-weight:bold; }
+ /*]]>*/-->
+</style>
+<script type="text/javascript">
+/*
+@licstart The following is the entire license notice for the
+JavaScript code in this tag.
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+
+The JavaScript code in this tag is free software: you can
+redistribute it and/or modify it under the terms of the GNU
+General Public License (GNU GPL) as published by the Free Software
+Foundation, either version 3 of the License, or (at your option)
+any later version. The code is distributed WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
+
+As additional permission under GNU GPL version 3 section 7, you
+may distribute non-source (e.g., minimized or compacted) forms of
+that code without the copy of the GNU GPL normally required by
+section 4, provided you include this license notice and a URL
+through which recipients can access the Corresponding Source.
+
+
+@licend The above is the entire license notice
+for the JavaScript code in this tag.
+*/
+<!--/*--><![CDATA[/*><!--*/
+ function CodeHighlightOn(elem, id)
+ {
+ var target = document.getElementById(id);
+ if(null != target) {
+ elem.cacheClassElem = elem.className;
+ elem.cacheClassTarget = target.className;
+ target.className = "code-highlighted";
+ elem.className = "code-highlighted";
+ }
+ }
+ function CodeHighlightOff(elem, id)
+ {
+ var target = document.getElementById(id);
+ if(elem.cacheClassElem)
+ elem.className = elem.cacheClassElem;
+ if(elem.cacheClassTarget)
+ target.className = elem.cacheClassTarget;
+ }
+/*]]>*///-->
+</script>
+
+</head>
+<body>
+
+<div id="preamble">
+
+</div>
+
+<div id="content">
+<h1 class="title">python-gnupg audit</h1>
+
+<p> <span class="timestamp-wrapper"> <span class="timestamp">2013-02-01 Fri</span></span><br/>
+</p>
+
+<div id="table-of-contents">
+<h2>Table of Contents</h2>
+<div id="text-table-of-contents">
+<ul>
+<li><a href="#sec-1">1 gnugp._<sub>main</sub>_<sub>()</sub></a>
+<ul>
+<li><a href="#sec-1-1">1.1 comments</a></li>
+<li><a href="#sec-1-2">1.2 def <sub>copy</sub><sub>data</sub>(instream, outstream)</a>
+<ul>
+<li><a href="#sec-1-2-1">1.2.1 L79:</a></li>
+<li><a href="#sec-1-2-2">1.2.2 L78:</a></li>
+<li><a href="#sec-1-2-3">1.2.3 L88:</a></li>
+</ul>
+</li>
+<li><a href="#sec-1-3">1.3 def <sub>threaded</sub><sub>copy</sub><sub>data</sub>(instream, outstream):</a>
+<ul>
+<li><a href="#sec-1-3-1">1.3.1 L99:</a></li>
+</ul>
+</li>
+<li><a href="#sec-1-4">1.4 def <sub>write</sub><sub>passphrase</sub>(stream, passphrase, encoding):</a>
+<ul>
+<li><a href="#sec-1-4-1">1.4.1 L110:</a></li>
+</ul></li>
+</ul>
+</li>
+<li><a href="#sec-2">2 class Verify(object)</a></li>
+<li><a href="#sec-3">3 class ImportResult(object)</a></li>
+<li><a href="#sec-4">4 class ListKeys(list):</a></li>
+<li><a href="#sec-5">5 class Crypt(Verify):</a>
+<ul>
+<li><a href="#sec-5-1">5.1 def _<sub>init</sub>_<sub>(self, gpg)</sub></a>
+<ul>
+<li><a href="#sec-5-1-1">5.1.1 L338</a></li>
+</ul></li>
+</ul>
+</li>
+<li><a href="#sec-6">6 class GenKey(object)</a></li>
+<li><a href="#sec-7">7 class DeleteResult(object)</a></li>
+<li><a href="#sec-8">8 class Sign(object)</a></li>
+<li><a href="#sec-9">9 class GPG(object)</a>
+<ul>
+<li>
+<ul>
+<li><a href="#sec-9-1">9.1 L474:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-1">9.1 def _<sub>init</sub>_<sub>(self, gpgbinary='gpg', gnupghome=None, verbose=False, use<sub>agent</sub>=False, keyring=None)</sub></a>
+<ul>
+<li><a href="#sec-9-1-1">9.1.1 L494-495:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-2">9.2 def <sub>open</sub><sub>subprocess</sub>(self, args, passphrase=False)</a>
+<ul>
+<li><a href="#sec-9-2-1">9.2.1 L515:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-3">9.3 def <sub>collect</sub><sub>output</sub>(self, process, result, writer=None, stdin=None)</a></li>
+<li><a href="#sec-9-4">9.4 def <sub>handle</sub><sub>io</sub>(self, args, file, result, passphrase=None, binary=False)</a>
+<ul>
+<li><a href="#sec-9-4-1">9.4.1 L601:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-5">9.5 def sign(self, message, **kwargs)</a>
+<ul>
+<li><a href="#sec-9-5-1">9.5.1 L617-619:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-6">9.6 def sign<sub>file</sub>(self, file, keyid=None, passphrase=None, clearsign=True, detach=False, binary=False)</a>
+<ul>
+<li><a href="#sec-9-6-1">9.6.1 L632-635:</a></li>
+<li><a href="#sec-9-6-2">9.6.2 L626-641:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-7">9.7 def verify(self, data):</a>
+<ul>
+<li><a href="#sec-9-7-1">9.7.1 L668-670:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-8">9.8 def verify<sub>file</sub>(self, file, data<sub>filename</sub>=None)</a>
+<ul>
+<li><a href="#sec-9-8-1">9.8.1 L683:</a></li>
+<li><a href="#sec-9-8-2">9.8.2 L684:</a></li>
+<li><a href="#sec-9-8-3">9.8.3 L690:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-9">9.9 def import<sub>keys</sub>(self, key<sub>data</sub>)</a>
+<ul>
+<li><a href="#sec-9-9-1">9.9.1 L749:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-10">9.10 def recieve<sub>keys</sub>(self, keyserver, *keyids)</a>
+<ul>
+<li><a href="#sec-9-10-1">9.10.1 L770:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-11">9.11 def export<sub>keys</sub>(self, keyids, secret=False)</a>
+<ul>
+<li><a href="#sec-9-11-1">9.11.1 L795-796:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-12">9.12 def list<sub>keys</sub>(self, secret=False)</a>
+<ul>
+<li><a href="#sec-9-12-1">9.12.1 L827:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-13">9.13 def gen<sub>key</sub>(self, input)</a>
+<ul>
+<li><a href="#sec-9-13-1">9.13.1 L864:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-14">9.14 def gen<sub>key</sub><sub>input</sub>(self, **kwargs)</a>
+<ul>
+<li><a href="#sec-9-14-1">9.14.1 L981-983:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-15">9.15 def encrypt<sub>file</sub>(self, file, recipiencts, sign=None, &hellip;)</a>
+<ul>
+<li><a href="#sec-9-15-1">9.15.1 L939:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-16">9.16 def encrypt(self, data, recipients, **kwargs):</a>
+<ul>
+<li><a href="#sec-9-16-1">9.16.1 L997:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-17">9.17 def decrypt(self, message **kwargs):</a>
+<ul>
+<li><a href="#sec-9-17-1">9.17.1 L1003:</a></li>
+</ul>
+</li>
+<li><a href="#sec-9-18">9.18 def decrypt<sub>file</sub>(self, file, always<sub>trust</sub>=False, passphrase=None, output=None)</a>
+<ul>
+<li><a href="#sec-9-18-1">9.18.1 L1013:</a></li>
+</ul></li>
+</ul>
+</li>
+<li><a href="#sec-10">10 POC</a></li>
+</ul>
+</div>
+</div>
+
+<div id="outline-container-1" class="outline-2">
+<h2 id="sec-1"><span class="section-number-2">1</span> gnugp._<sub>main</sub>_<sub>()</sub></h2>
+<div class="outline-text-2" id="text-1">
+
+
+</div>
+
+<div id="outline-container-1-1" class="outline-3">
+<h3 id="sec-1-1"><span class="section-number-3">1.1</span> comments</h3>
+<div class="outline-text-3" id="text-1-1">
+
+<p>L58 NullHandler?? see self.<sub>write</sub><sub>passphrase</sub>
+L61 there nifty check for p3k
+</p></div>
+
+</div>
+
+<div id="outline-container-1-2" class="outline-3">
+<h3 id="sec-1-2"><span class="section-number-3">1.2</span> def <sub>copy</sub><sub>data</sub>(instream, outstream) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-1-2">
+
+<p> copies data from one stream to another, 1024 bytes at a time.
+</p>
+</div>
+
+<div id="outline-container-1-2-1" class="outline-4">
+<h4 id="sec-1-2-1"><span class="section-number-4">1.2.1</span> L79: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="bad_logic">bad_logic</span></span></h4>
+<div class="outline-text-4" id="text-1-2-1">
+
+<p> instream is apparently a file descriptor, but is not checked nor
+ encased in a try/except block.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-2-2" class="outline-4">
+<h4 id="sec-1-2-2"><span class="section-number-4">1.2.2</span> L78: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="hanging_fd">hanging_fd</span>&nbsp;<span class="bad_logic">bad_logic</span></span></h4>
+<div class="outline-text-4" id="text-1-2-2">
+
+<p> while True: loop, should be
+</p><pre class="example">
+with open(instream) as instrm:
+</pre>
+
+</div>
+
+</div>
+
+<div id="outline-container-1-2-3" class="outline-4">
+<h4 id="sec-1-2-3"><span class="section-number-4">1.2.3</span> L88: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="bad_exception_handling">bad_exception_handling</span></span></h4>
+<div class="outline-text-4" id="text-1-2-3">
+
+<pre class="example">
+except:
+</pre>
+
+<p> should catch an IOError, or whatever specific error is raised for broken
+ pipes.
+</p></div>
+</div>
+
+</div>
+
+<div id="outline-container-1-3" class="outline-3">
+<h3 id="sec-1-3"><span class="section-number-3">1.3</span> def <sub>threaded</sub><sub>copy</sub><sub>data</sub>(instream, outstream):</h3>
+<div class="outline-text-3" id="text-1-3">
+
+
+</div>
+
+<div id="outline-container-1-3-1" class="outline-4">
+<h4 id="sec-1-3-1"><span class="section-number-4">1.3.1</span> L99:</h4>
+<div class="outline-text-4" id="text-1-3-1">
+
+<p> this just wraps self.<sub>copy</sub><sub>data</sub> in a thread
+</p></div>
+</div>
+
+</div>
+
+<div id="outline-container-1-4" class="outline-3">
+<h3 id="sec-1-4"><span class="section-number-3">1.4</span> def <sub>write</sub><sub>passphrase</sub>(stream, passphrase, encoding): &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span>&nbsp;<span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-1-4">
+
+
+</div>
+
+<div id="outline-container-1-4-1" class="outline-4">
+<h4 id="sec-1-4-1"><span class="section-number-4">1.4.1</span> L110: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="writes_passphrase_to_disk">writes_passphrase_to_disk</span></span></h4>
+<div class="outline-text-4" id="text-1-4-1">
+
+<p> logger writes passphrase into debug log. this should be patched.
+</p></div>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2" class="outline-2">
+<h2 id="sec-2"><span class="section-number-2">2</span> class Verify(object)</h2>
+<div class="outline-text-2" id="text-2">
+
+<p> basic parsing class, no errors found
+</p></div>
+
+</div>
+
+<div id="outline-container-3" class="outline-2">
+<h2 id="sec-3"><span class="section-number-2">3</span> class ImportResult(object)</h2>
+<div class="outline-text-2" id="text-3">
+
+<p> basic parsing class, no errors found
+</p></div>
+
+</div>
+
+<div id="outline-container-4" class="outline-2">
+<h2 id="sec-4"><span class="section-number-2">4</span> class ListKeys(list):</h2>
+<div class="outline-text-2" id="text-4">
+
+<p> basic parsing class, no errors found
+</p></div>
+
+</div>
+
+<div id="outline-container-5" class="outline-2">
+<h2 id="sec-5"><span class="section-number-2">5</span> class Crypt(Verify):</h2>
+<div class="outline-text-2" id="text-5">
+
+<p> basic parsing class, no errors found
+</p>
+</div>
+
+<div id="outline-container-5-1" class="outline-3">
+<h3 id="sec-5-1"><span class="section-number-3">5.1</span> def _<sub>init</sub>_<sub>(self, gpg)</sub> &nbsp;&nbsp;&nbsp;<span class="tag"><span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-5-1">
+
+
+</div>
+
+<div id="outline-container-5-1-1" class="outline-4">
+<h4 id="sec-5-1-1"><span class="section-number-4">5.1.1</span> L338 &nbsp;&nbsp;&nbsp;<span class="tag"><span class="mro_conflict">mro_conflict</span></span></h4>
+<div class="outline-text-4" id="text-5-1-1">
+
+
+
+
+
+<pre class="src src-python">Verify.__init__(<span style="color: #00cdcd; font-weight: bold;">self</span>,gpg)
+</pre>
+
+
+<p>
+ should be changed to:
+</p>
+
+
+
+<pre class="src src-python"><span style="color: #0000ee; font-weight: bold;">super</span>(Verify, <span style="color: #00cdcd; font-weight: bold;">self</span>).__init__(gpg)
+</pre>
+
+</div>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-6" class="outline-2">
+<h2 id="sec-6"><span class="section-number-2">6</span> class GenKey(object)</h2>
+<div class="outline-text-2" id="text-6">
+
+<p> basic parsing class, no errors found
+</p></div>
+
+</div>
+
+<div id="outline-container-7" class="outline-2">
+<h2 id="sec-7"><span class="section-number-2">7</span> class DeleteResult(object)</h2>
+<div class="outline-text-2" id="text-7">
+
+<p> basic parsing class, no errors found
+</p></div>
+
+</div>
+
+<div id="outline-container-8" class="outline-2">
+<h2 id="sec-8"><span class="section-number-2">8</span> class Sign(object)</h2>
+<div class="outline-text-2" id="text-8">
+
+<p> basic parsing class, no errors found
+</p></div>
+
+</div>
+
+<div id="outline-container-9" class="outline-2">
+<h2 id="sec-9"><span class="section-number-2">9</span> class GPG(object) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="exploitable">exploitable</span></span></h2>
+<div class="outline-text-2" id="text-9">
+
+
+</div>
+
+<div id="outline-container-9-1" class="outline-4">
+<h4 id="sec-9-1"><span class="section-number-4">9.1</span> L474: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="cleanup">cleanup</span></span></h4>
+<div class="outline-text-4" id="text-9-1">
+
+<pre class="example">
+cls.__doc__
+</pre>
+
+<p> should go directly underneath class signature
+</p></div>
+
+</div>
+
+<div id="outline-container-9-1" class="outline-3">
+<h3 id="sec-9-1"><span class="section-number-3">9.1</span> def _<sub>init</sub>_<sub>(self, gpgbinary='gpg', gnupghome=None, verbose=False, use<sub>agent</sub>=False, keyring=None)</sub> &nbsp;&nbsp;&nbsp;<span class="tag"><span class="bug">bug</span></span></h3>
+<div class="outline-text-3" id="text-9-1">
+
+
+</div>
+
+<div id="outline-container-9-1-1" class="outline-4">
+<h4 id="sec-9-1-1"><span class="section-number-4">9.1.1</span> L494-495: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="type_error">type_error</span></span></h4>
+<div class="outline-text-4" id="text-9-1-1">
+
+
+
+
+
+<pre class="src src-python"><span style="color: #00cdcd; font-weight: bold;">if</span> gnupghome <span style="color: #00cdcd; font-weight: bold;">and</span> <span style="color: #00cdcd; font-weight: bold;">not</span> os.path.isdir(<span style="color: #00cdcd; font-weight: bold;">self</span>.gnupghome):
+ os.makedirs(<span style="color: #00cdcd; font-weight: bold;">self</span>.gnupghome,0x1C0)
+</pre>
+
+
+
+<pre class="example">In [20]: os.makedirs?
+Type: function
+String Form:&lt;function makedirs at 0x7f8ddeb6cc08&gt;
+File: /usr/lib/python2.7/os.py
+Definition: os.makedirs(name, mode=511)
+Docstring:
+makedirs(path [, mode=0777])
+Super-mkdir; create a leaf directory and all intermediate ones.
+Works like mkdir, except that any intermediate path segment (not
+just the rightmost) will be created if it does not exist. This is
+recursive.
+
+setting mode=0x1c0 is equivalent to mode=hex(0700), which
+may cause bugs on some systems, see
+http://ubuntuforums.org/showthread.php?t=2044879
+
+this could be do to the complete lack of input validation in
+os.makedirs, and it's calling of the os.mkdir() built-in, which
+may vary depending on the python compilation:
+</pre>
+
+
+
+<pre class="src src-python">Source:
+<span style="color: #00cdcd; font-weight: bold;">def</span> <span style="color: #0000ee; font-weight: bold;">makedirs</span>(name, mode=0777):
+ <span style="color: #00cd00;">"""makedirs(path [, mode=0777])</span>
+
+<span style="color: #00cd00;"> Super-mkdir; create a leaf directory and all intermediate ones.</span>
+<span style="color: #00cd00;"> Works like mkdir, except that any intermediate path segment (not</span>
+<span style="color: #00cd00;"> just the rightmost) will be created if it does not exist. This is</span>
+<span style="color: #00cd00;"> recursive.</span>
+<span style="color: #00cd00;"> """</span>
+ <span style="color: #cdcd00;">head</span>, <span style="color: #cdcd00;">tail</span> = path.split(name)
+ <span style="color: #00cdcd; font-weight: bold;">if</span> <span style="color: #00cdcd; font-weight: bold;">not</span> tail:
+ <span style="color: #cdcd00;">head</span>, <span style="color: #cdcd00;">tail</span> = path.split(head)
+ <span style="color: #00cdcd; font-weight: bold;">if</span> head <span style="color: #00cdcd; font-weight: bold;">and</span> tail <span style="color: #00cdcd; font-weight: bold;">and</span> <span style="color: #00cdcd; font-weight: bold;">not</span> path.exists(head):
+ <span style="color: #00cdcd; font-weight: bold;">try</span>:
+ makedirs(head, mode)
+ <span style="color: #00cdcd; font-weight: bold;">except</span> <span style="color: #00cd00;">OSError</span>, e:
+ <span style="color: #cdcd00;"># </span><span style="color: #cdcd00;">be happy if someone already created the path</span>
+ <span style="color: #00cdcd; font-weight: bold;">if</span> e.errno != errno.EEXIST:
+ <span style="color: #00cdcd; font-weight: bold;">raise</span>
+ <span style="color: #00cdcd; font-weight: bold;">if</span> tail == curdir: <span style="color: #cdcd00;"># </span><span style="color: #cdcd00;">xxx/newdir/. exists if xxx/newdir exists</span>
+ <span style="color: #00cdcd; font-weight: bold;">return</span>
+ mkdir(name, mode)
+</pre>
+
+
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-2" class="outline-3">
+<h3 id="sec-9-2"><span class="section-number-3">9.2</span> def <sub>open</sub><sub>subprocess</sub>(self, args, passphrase=False) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-2">
+
+
+</div>
+
+<div id="outline-container-9-2-1" class="outline-4">
+<h4 id="sec-9-2-1"><span class="section-number-4">9.2.1</span> L515: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-2-1">
+
+<pre class="example">
+cmd.extend(args)
+</pre>
+
+
+<p>
+ cmd is a list of strings, eventually joined with cmd=' '.join(cmd), and
+ the args are unvalidated in this function. Then this concatenation of args
+ is fed directly into subprocess.Popen(cmd, shell=True, stdin=PIPE,
+ stdout=PIPE, stderr=PIPE). THIS SHOULD BE PATCHED.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-3" class="outline-3">
+<h3 id="sec-9-3"><span class="section-number-3">9.3</span> def <sub>collect</sub><sub>output</sub>(self, process, result, writer=None, stdin=None)</h3>
+<div class="outline-text-3" id="text-9-3">
+
+<p> sends stdout to self.<sub>read</sub><sub>data</sub>() and stderr to self.<sub>read</sub><sub>response</sub>()
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-9-4" class="outline-3">
+<h3 id="sec-9-4"><span class="section-number-3">9.4</span> def <sub>handle</sub><sub>io</sub>(self, args, file, result, passphrase=None, binary=False) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span>&nbsp;<span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-9-4">
+
+
+</div>
+
+<div id="outline-container-9-4-1" class="outline-4">
+<h4 id="sec-9-4-1"><span class="section-number-4">9.4.1</span> L601: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span>&nbsp;<span class="type_check_in_call">type_check_in_call</span></span></h4>
+<div class="outline-text-4" id="text-9-4-1">
+
+<pre class="example">
+p = self._open_subprocess(args, passphrase is not None)
+</pre>
+
+
+<p>
+ you shouldn't assign or type check in a function call
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-5" class="outline-3">
+<h3 id="sec-9-5"><span class="section-number-3">9.5</span> def sign(self, message, **kwargs) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-9-5">
+
+
+</div>
+
+<div id="outline-container-9-5-1" class="outline-4">
+<h4 id="sec-9-5-1"><span class="section-number-4">9.5.1</span> L617-619: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="hanging_fd">hanging_fd</span></span></h4>
+<div class="outline-text-4" id="text-9-5-1">
+
+<p> calls self.<sub>make</sub><sub>binary</sub><sub>stream</sub>(), which leaves the file descriptor for
+ the encoded message to be encrypted hanging between scopes.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-6" class="outline-3">
+<h3 id="sec-9-6"><span class="section-number-3">9.6</span> def sign<sub>file</sub>(self, file, keyid=None, passphrase=None, clearsign=True, detach=False, binary=False) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-9-6">
+
+
+</div>
+
+<div id="outline-container-9-6-1" class="outline-4">
+<h4 id="sec-9-6-1"><span class="section-number-4">9.6.1</span> L632-635: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="bad_logic">bad_logic</span></span></h4>
+<div class="outline-text-4" id="text-9-6-1">
+
+
+
+
+<pre class="src src-python"><span style="color: #00cdcd; font-weight: bold;">if</span> detach:
+ args.append(<span style="color: #00cd00;">"--detach-sign"</span>)
+<span style="color: #00cdcd; font-weight: bold;">elif</span> clearsign:
+ args.append(<span style="color: #00cd00;">"--clearsign"</span>)
+</pre>
+
+
+<p>
+ the logic here allows that if a user erroneously specifies both options,
+ rather than doing what the system gnupg would do (that is, do &ndash;clearsign,
+ and ignore the &ndash;attach-sign), python-gnupg would ignore both.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-9-6-2" class="outline-4">
+<h4 id="sec-9-6-2"><span class="section-number-4">9.6.2</span> L626-641:</h4>
+<div class="outline-text-4" id="text-9-6-2">
+
+<p> input 'args' into self.<sub>open</sub><sub>subprocess</sub>() is defined as static strings.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-7" class="outline-3">
+<h3 id="sec-9-7"><span class="section-number-3">9.7</span> def verify(self, data): &nbsp;&nbsp;&nbsp;<span class="tag"><span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-9-7">
+
+
+</div>
+
+<div id="outline-container-9-7-1" class="outline-4">
+<h4 id="sec-9-7-1"><span class="section-number-4">9.7.1</span> L668-670: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="hanging_fd">hanging_fd</span></span></h4>
+<div class="outline-text-4" id="text-9-7-1">
+
+<p> same hanging file descriptor problem as in self.sign()
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-8" class="outline-3">
+<h3 id="sec-9-8"><span class="section-number-3">9.8</span> def verify<sub>file</sub>(self, file, data<sub>filename</sub>=None) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span>&nbsp;<span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-9-8">
+
+
+</div>
+
+<div id="outline-container-9-8-1" class="outline-4">
+<h4 id="sec-9-8-1"><span class="section-number-4">9.8.1</span> L683: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="hanging_fd">hanging_fd</span></span></h4>
+<div class="outline-text-4" id="text-9-8-1">
+
+<p> more potentially hanging file descriptors&hellip;
+</p></div>
+
+</div>
+
+<div id="outline-container-9-8-2" class="outline-4">
+<h4 id="sec-9-8-2"><span class="section-number-4">9.8.2</span> L684: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="hanging_fd">hanging_fd</span></span></h4>
+<div class="outline-text-4" id="text-9-8-2">
+
+<p> oh look, another hanging file descriptor. imagine that.
+</p></div>
+
+</div>
+
+<div id="outline-container-9-8-3" class="outline-4">
+<h4 id="sec-9-8-3"><span class="section-number-4">9.8.3</span> L690: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-8-3">
+
+<pre class="example">
+args.append('"%s"' % data_filename)
+</pre>
+
+<p> well, there's the exploit. see included POC script.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-9" class="outline-3">
+<h3 id="sec-9-9"><span class="section-number-3">9.9</span> def import<sub>keys</sub>(self, key<sub>data</sub>) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-9">
+
+
+</div>
+
+<div id="outline-container-9-9-1" class="outline-4">
+<h4 id="sec-9-9-1"><span class="section-number-4">9.9.1</span> L749: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-9-1">
+
+<p> this function could potentially allow an attacker with a GPG exploit to
+ use it, because it passes key generation parameter directly into the
+ internal packet parsers of GPG. however, without a GPG exploit for one of
+ the GPG packet parsers (for explanation of GPG packets look into pgpdump),
+ this function alone is not exploitable.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-10" class="outline-3">
+<h3 id="sec-9-10"><span class="section-number-3">9.10</span> def recieve<sub>keys</sub>(self, keyserver, *keyids) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-10">
+
+
+</div>
+
+<div id="outline-container-9-10-1" class="outline-4">
+<h4 id="sec-9-10-1"><span class="section-number-4">9.10.1</span> L770: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-10-1">
+
+<pre class="example">
+args.extend(keyids)
+</pre>
+
+
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-11" class="outline-3">
+<h3 id="sec-9-11"><span class="section-number-3">9.11</span> def export<sub>keys</sub>(self, keyids, secret=False) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-11">
+
+
+</div>
+
+<div id="outline-container-9-11-1" class="outline-4">
+<h4 id="sec-9-11-1"><span class="section-number-4">9.11.1</span> L795-796: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-11-1">
+
+<p> args problem again. exploitable though parameter ``keyids``.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-12" class="outline-3">
+<h3 id="sec-9-12"><span class="section-number-3">9.12</span> def list<sub>keys</sub>(self, secret=False)</h3>
+<div class="outline-text-3" id="text-9-12">
+
+
+</div>
+
+<div id="outline-container-9-12-1" class="outline-4">
+<h4 id="sec-9-12-1"><span class="section-number-4">9.12.1</span> L827:</h4>
+<div class="outline-text-4" id="text-9-12-1">
+
+<p> args is static string.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-13" class="outline-3">
+<h3 id="sec-9-13"><span class="section-number-3">9.13</span> def gen<sub>key</sub>(self, input) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="cleanup">cleanup</span></span></h3>
+<div class="outline-text-3" id="text-9-13">
+
+
+</div>
+
+<div id="outline-container-9-13-1" class="outline-4">
+<h4 id="sec-9-13-1"><span class="section-number-4">9.13.1</span> L864:</h4>
+<div class="outline-text-4" id="text-9-13-1">
+
+<p> args, passed to self.<sub>handle</sub><sub>io</sub>(), which in turn passes args directly to
+ Popen(), is set to a static string. this function is halfway okay, though
+ it really could be more careful with the ``input`` parameter.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-14" class="outline-3">
+<h3 id="sec-9-14"><span class="section-number-3">9.14</span> def gen<sub>key</sub><sub>input</sub>(self, **kwargs) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-14">
+
+
+</div>
+
+<div id="outline-container-9-14-1" class="outline-4">
+<h4 id="sec-9-14-1"><span class="section-number-4">9.14.1</span> L981-983: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-14-1">
+
+<p> this function could potentially allow an attacker with a GPG exploit to
+ use it, because it passes key generation parameter directly into the
+ internal packet parsers of GPG. however, without a GPG exploit for one of
+ the GPG packet parsers (for explanation of GPG packets look into pgpdump),
+ this function alone is not exploitable.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-15" class="outline-3">
+<h3 id="sec-9-15"><span class="section-number-3">9.15</span> def encrypt<sub>file</sub>(self, file, recipiencts, sign=None, &hellip;) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-15">
+
+
+</div>
+
+<div id="outline-container-9-15-1" class="outline-4">
+<h4 id="sec-9-15-1"><span class="section-number-4">9.15.1</span> L939: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-15-1">
+
+<p> several of the inputs to this function are unvalidated, turned into
+ strings, and passed to Popen(). exploitable.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-16" class="outline-3">
+<h3 id="sec-9-16"><span class="section-number-3">9.16</span> def encrypt(self, data, recipients, **kwargs): &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-16">
+
+
+</div>
+
+<div id="outline-container-9-16-1" class="outline-4">
+<h4 id="sec-9-16-1"><span class="section-number-4">9.16.1</span> L997: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-16-1">
+
+<p> exploitable, passes kwargs to self.encrypt<sub>file</sub>()
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-17" class="outline-3">
+<h3 id="sec-9-17"><span class="section-number-3">9.17</span> def decrypt(self, message **kwargs): &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-17">
+
+
+</div>
+
+<div id="outline-container-9-17-1" class="outline-4">
+<h4 id="sec-9-17-1"><span class="section-number-4">9.17.1</span> L1003: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-17-1">
+
+<p> kwargs are passed to self.decrypt<sub>file</sub>(), unvalidated, making this
+ function also exploitable
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-9-18" class="outline-3">
+<h3 id="sec-9-18"><span class="section-number-3">9.18</span> def decrypt<sub>file</sub>(self, file, always<sub>trust</sub>=False, passphrase=None, output=None) &nbsp;&nbsp;&nbsp;<span class="tag"><span class="vuln">vuln</span></span></h3>
+<div class="outline-text-3" id="text-9-18">
+
+
+</div>
+
+<div id="outline-container-9-18-1" class="outline-4">
+<h4 id="sec-9-18-1"><span class="section-number-4">9.18.1</span> L1013: &nbsp;&nbsp;&nbsp;<span class="tag"><span class="unvalidated_user_input">unvalidated_user_input</span></span></h4>
+<div class="outline-text-4" id="text-9-18-1">
+
+<p> unvalidated user input: this function is also exploitable
+</p>
+</div>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-10" class="outline-2">
+<h2 id="sec-10"><span class="section-number-2">10</span> POC</h2>
+<div class="outline-text-2" id="text-10">
+
+<p>CANNOT INCLUDE FILE ../python-gnupg-0.3.1/python-gnupg-exploit.py
+</p></div>
+</div>
+</div>
+
+<div id="postamble">
+<p class="date">Date: 2013-02-01 Fri</p>
+<p class="author">Author: isis</p>
+<p class="email"><a href="mailto:isis@leap.se">isis@leap.se</a></p>
+<p class="creator"><a href="http://orgmode.org">Org</a> version 7.9.2 with <a href="http://www.gnu.org/software/emacs/">Emacs</a> version 24</p>
+<a href="http://validator.w3.org/check?uri=referer">Validate XHTML 1.0</a>
+
+</div>
+</body>
+</html>
diff --git a/docs/NOTES-python-gnupg-3.1-audit.org b/docs/NOTES-python-gnupg-3.1-audit.org
new file mode 100644
index 0000000..b80fb39
--- /dev/null
+++ b/docs/NOTES-python-gnupg-3.1-audit.org
@@ -0,0 +1,232 @@
+#+TITLE: python-gnupg audit
+#+AUTHOR: isis
+#+EMAIL: isis@leap.se
+#+DATE: 2013-02-01 Fri
+#+DESCRIPTION:
+#+KEYWORDS:
+#+LANGUAGE: en
+#+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t
+#+OPTIONS: TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc
+#+INFOJS_OPT: view:nil toc:2 ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
+#+EXPORT_SELECT_TAGS: export
+#+EXPORT_EXCLUDE_TAGS: noexport
+#+LINK_UP:
+#+LINK_HOME:
+#+XSLT:
+
+[2013-02-01 Fri]
+
+* gnugp.__main__()
+** comments
+L58 NullHandler?? see self._write_passphrase
+L61 there nifty check for p3k
+** def _copy_data(instream, outstream) :cleanup:
+ copies data from one stream to another, 1024 bytes at a time.
+*** L79: :bad_logic:
+ instream is apparently a file descriptor, but is not checked nor
+ encased in a try/except block.
+
+*** L78: :hanging_fd:bad_logic:
+ while True: loop, should be
+ : with open(instream) as instrm:
+*** L88: :bad_exception_handling:
+ : except:
+ should catch an IOError, or whatever specific error is raised for broken
+ pipes.
+** def _threaded_copy_data(instream, outstream):
+*** L99:
+ this just wraps self._copy_data in a thread
+** def _write_passphrase(stream, passphrase, encoding): :vuln:cleanup:
+*** L110: :writes_passphrase_to_disk:
+ logger writes passphrase into debug log. this should be patched.
+* class Verify(object)
+ basic parsing class, no errors found
+* class ImportResult(object)
+ basic parsing class, no errors found
+* class ListKeys(list):
+ basic parsing class, no errors found
+* class Crypt(Verify):
+ basic parsing class, no errors found
+** def __init__(self, gpg) :cleanup:
+*** L338 :mro_conflict:
+
+ #+BEGIN_SRC python
+ Verify.__init__(self,gpg)
+ #+END_SRC
+
+ should be changed to:
+
+ #+BEGIN_SRC python
+ super(Verify, self).__init__(gpg)
+ #+END_SRC
+* class GenKey(object)
+ basic parsing class, no errors found
+* class DeleteResult(object)
+ basic parsing class, no errors found
+* class Sign(object)
+ basic parsing class, no errors found
+* class GPG(object) :exploitable:
+*** L474: :cleanup:
+ : cls.__doc__
+ should go directly underneath class signature
+** def __init__(self, gpgbinary='gpg', gnupghome=None, verbose=False, use_agent=False, keyring=None) :bug:
+*** L494-495: :type_error:
+
+ #+BEGIN_SRC python
+ if gnupghome and not os.path.isdir(self.gnupghome):
+ os.makedirs(self.gnupghome,0x1C0)
+ #+END_SRC
+
+ #+BEGIN_EXAMPLE
+ In [20]: os.makedirs?
+ Type: function
+ String Form:<function makedirs at 0x7f8ddeb6cc08>
+ File: /usr/lib/python2.7/os.py
+ Definition: os.makedirs(name, mode=511)
+ Docstring:
+ makedirs(path [, mode=0777])
+ Super-mkdir; create a leaf directory and all intermediate ones.
+ Works like mkdir, except that any intermediate path segment (not
+ just the rightmost) will be created if it does not exist. This is
+ recursive.
+
+ setting mode=0x1c0 is equivalent to mode=hex(0700), which
+ may cause bugs on some systems, see
+ http://ubuntuforums.org/showthread.php?t=2044879
+
+ this could be do to the complete lack of input validation in
+ os.makedirs, and it's calling of the os.mkdir() built-in, which
+ may vary depending on the python compilation:
+ #+END_EXAMPLE
+
+ #+BEGIN_SRC python
+ Source:
+ def makedirs(name, mode=0777):
+ """makedirs(path [, mode=0777])
+
+ Super-mkdir; create a leaf directory and all intermediate ones.
+ Works like mkdir, except that any intermediate path segment (not
+ just the rightmost) will be created if it does not exist. This is
+ recursive.
+ """
+ head, tail = path.split(name)
+ if not tail:
+ head, tail = path.split(head)
+ if head and tail and not path.exists(head):
+ try:
+ makedirs(head, mode)
+ except OSError, e:
+ # be happy if someone already created the path
+ if e.errno != errno.EEXIST:
+ raise
+ if tail == curdir: # xxx/newdir/. exists if xxx/newdir exists
+ return
+ mkdir(name, mode)
+ #+END_SRC
+
+** def _open_subprocess(self, args, passphrase=False) :vuln:
+*** L515: :unvalidated_user_input:
+ : cmd.extend(args)
+
+ cmd is a list of strings, eventually joined with cmd=' '.join(cmd), and
+ the args are unvalidated in this function. Then this concatenation of args
+ is fed directly into subprocess.Popen(cmd, shell=True, stdin=PIPE,
+ stdout=PIPE, stderr=PIPE). THIS SHOULD BE PATCHED.
+
+** def _collect_output(self, process, result, writer=None, stdin=None)
+ sends stdout to self._read_data() and stderr to self._read_response()
+
+** def _handle_io(self, args, file, result, passphrase=None, binary=False) :vuln:cleanup:
+*** L601: :unvalidated_user_input:type_check_in_call:
+ : p = self._open_subprocess(args, passphrase is not None)
+
+ you shouldn't assign or type check in a function call
+
+** def sign(self, message, **kwargs) :cleanup:
+*** L617-619: :hanging_fd:
+ calls self._make_binary_stream(), which leaves the file descriptor for
+ the encoded message to be encrypted hanging between scopes.
+
+** def sign_file(self, file, keyid=None, passphrase=None, clearsign=True, detach=False, binary=False) :cleanup:
+*** L632-635: :bad_logic:
+ #+BEGIN_SRC python
+ if detach:
+ args.append("--detach-sign")
+ elif clearsign:
+ args.append("--clearsign")
+ #+END_SRC
+
+ the logic here allows that if a user erroneously specifies both options,
+ rather than doing what the system gnupg would do (that is, do --clearsign,
+ and ignore the --attach-sign), python-gnupg would ignore both.
+
+*** L626-641:
+ input 'args' into self._open_subprocess() is defined as static strings.
+
+** def verify(self, data): :cleanup:
+*** L668-670: :hanging_fd:
+ same hanging file descriptor problem as in self.sign()
+
+** def verify_file(self, file, data_filename=None) :vuln:cleanup:
+*** L683: :hanging_fd:
+ more potentially hanging file descriptors...
+*** L684: :hanging_fd:
+ oh look, another hanging file descriptor. imagine that.
+*** L690: :unvalidated_user_input:
+ : args.append('"%s"' % data_filename)
+ well, there's the exploit. see included POC script.
+
+** def import_keys(self, key_data) :vuln:
+*** L749: :unvalidated_user_input:
+ this function could potentially allow an attacker with a GPG exploit to
+ use it, because it passes key generation parameter directly into the
+ internal packet parsers of GPG. however, without a GPG exploit for one of
+ the GPG packet parsers (for explanation of GPG packets look into pgpdump),
+ this function alone is not exploitable.
+
+** def recieve_keys(self, keyserver, *keyids) :vuln:
+*** L770: :unvalidated_user_input:
+ : args.extend(keyids)
+
+** def export_keys(self, keyids, secret=False) :vuln:
+*** L795-796: :unvalidated_user_input:
+ args problem again. exploitable though parameter ``keyids``.
+
+** def list_keys(self, secret=False)
+*** L827:
+ args is static string.
+
+** def gen_key(self, input) :cleanup:
+*** L864:
+ args, passed to self._handle_io(), which in turn passes args directly to
+ Popen(), is set to a static string. this function is halfway okay, though
+ it really could be more careful with the ``input`` parameter.
+
+** def gen_key_input(self, **kwargs) :vuln:
+*** L981-983: :unvalidated_user_input:
+ this function could potentially allow an attacker with a GPG exploit to
+ use it, because it passes key generation parameter directly into the
+ internal packet parsers of GPG. however, without a GPG exploit for one of
+ the GPG packet parsers (for explanation of GPG packets look into pgpdump),
+ this function alone is not exploitable.
+
+** def encrypt_file(self, file, recipiencts, sign=None, ...) :vuln:
+*** L939: :unvalidated_user_input:
+ several of the inputs to this function are unvalidated, turned into
+ strings, and passed to Popen(). exploitable.
+
+** def encrypt(self, data, recipients, **kwargs): :vuln:
+*** L997: :unvalidated_user_input:
+ exploitable, passes kwargs to self.encrypt_file()
+
+** def decrypt(self, message **kwargs): :vuln:
+*** L1003: :unvalidated_user_input:
+ kwargs are passed to self.decrypt_file(), unvalidated, making this
+ function also exploitable
+
+** def decrypt_file(self, file, always_trust=False, passphrase=None, output=None) :vuln:
+*** L1013: :unvalidated_user_input:
+ unvalidated user input: this function is also exploitable
+
+* POC
+#+INCLUDE: "../python-gnupg-0.3.1/python-gnupg-exploit.py" python
diff --git a/docs/NOTES-python-openpgp-implementations.txt b/docs/NOTES-python-openpgp-implementations.txt
new file mode 100644
index 0000000..bf60728
--- /dev/null
+++ b/docs/NOTES-python-openpgp-implementations.txt
@@ -0,0 +1,31 @@
+-*- mode: org -*-
+
+* Other Python OpenPGP libraries and utilities:
+
+*** pygpgme - https://launchpad.net/pygpgme
+A limited set of Python wrappers around GPGME
+http://www.gnupg.org/documentation/manuals/gpgme/
+
+*** py-gnupg - https://github.com/kevinoid/py-gnupg/blob/master/GnuPGInterface.py
+Focuses mainly on using file handles to interact with GnuPG.
+
+*** OpenPGP-Python - https://github.com/singpolyma/OpenPGP-Python
+The commit messages are a bit worrysome and the code has some scary
+error-prone-looking method chaining going on, a five minute glance over the
+/OpenPGP/Crypto.py file and it appears this is actually a valid OpenPGP
+implementation, built using D.Litzenberger's PyCrypto library.
+https://github.com/dlitz/pycrypto
+
+This person also wrote OpenPGP-Haskell:
+https://github.com/singpolyma/OpenPGP-Haskell
+
+...and OpenPGP-PHP (/horrorface): https://github.com/singpolyma/openpgp-php and
+an HTTP server as a shell script with a pretty crazy pipe hack.
+
+...and kudos on this one, it's an attempt at a mnemnonic system for squaring
+Zooko's Triangle (L17 being a function named "countLeadingCrapAndZeros"):
+https://github.com/singpolyma/mnemonicode/blob/master/mnencode.c#L17
+
+* GnuPG unattended key generation scripts:
+
+*** mandos-keygen http://bzr.recompile.se/loggerhead/mandos/trunk/annotate/523/mandos-keygen?start_revid=616
diff --git a/docs/OpenPGP-keys-in-DNS.md b/docs/OpenPGP-keys-in-DNS.md
new file mode 100644
index 0000000..56cb542
--- /dev/null
+++ b/docs/OpenPGP-keys-in-DNS.md
@@ -0,0 +1,133 @@
+[Christoph Berg's Blog](../index.html)/
+
+[2007](../2007.html)/
+
+</span>
+<span class="title">
+OpenPGP keys in DNS
+
+</span>
+</span>
+
+</div>
+
+<div class="actions">
+
+* [RecentChanges](../recentchanges.html)
+* [History](http://svn.df7cb.de/viewcvs.cgi/trunk/2007/openpgp-dns.mdwn?root=blog&view=log)
+</div>
+
+</div>
+
+<div id="pagebody">
+
+<div id="content">
+
+The latest addition to the mutt CVS tree is PKA support via gpgme. While trying
+to figure out how that works in mutt (I haven't yet...) I configured my DNS
+server for PKA and CERT records.
+
+## PKA
+
+PKA (public key association) puts a pointer where to obtain a key into a TXT
+record. At the same time that can be used to verify that a key belongs to a
+mail address. The documentation is at the
+[g10code website](http://www.g10code.de/docs/pka-intro.de.pdf)
+(only in German so far). I put the following into the df7cb.de zone:
+
+<p>
+cb._pka IN TXT "v=pka1;fpr=D224C8B07E63A6946DA32E07C5AF774A58510B5A;uri=finger:cb@df7cb.de"
+
+<pre>
+$ host -t TXT cb._pka.df7cb.de
+cb._pka.df7cb.de descriptive text "v=pka1\;fpr=D224C8B07E63A6946DA32E07C5AF774A58510B5A\;uri=finger:cb@df7cb.de"
+</pre>
+
+Now gpg can be told to use PKA to find the key:
+
+<pre>
+$ echo foo | gpg --auto-key-locate pka --recipient cb@df7cb.de --encrypt -a
+gpg: no keyserver known (use option --keyserver)
+gpg: requesting key 58510B5A from finger:cb@df7cb.de
+gpg: key 58510B5A: public key "Christoph Berg " imported
+gpg: Total number processed: 1
+gpg: imported: 1
+gpg: automatically retrieved `cb@df7cb.de' via PKA
+</pre>
+
+## CERT
+
+CERT records work similarly. Records are generated by make-dns-cert (from the
+tools directory in the gnupg source). cb.gpg is a stripped-down gpg keyring
+(created with pgp-clean -s and converting from .asc to .gpg).
+
+<pre>
+$ ./make-dns-cert -f D224C8B07E63A6946DA32E07C5AF774A58510B5A -n cb
+cb TYPE37 \# 26 0006 0000 00 14 D224C8B07E63A6946DA32E07C5AF774A58510B5A
+$ ./make-dns-cert -k cb.gpg -n cb
+cb TYPE37 \# 1338 0003 0000 00 9901A20440 [...] 509C96D4BFF17B7
+</pre>
+
+With a new bind and host (backports.org!) the format looks a bit nicer, that's
+also what I copied into the zone file:
+
+<pre>
+$ host -t CERT cb.df7cb.de
+;; Truncated, retrying in TCP mode.
+cb.df7cb.de has CERT record PGP 0 0 mQGiBECBGdAR [...] UDlCcltS/8Xtw==
+cb.df7cb.de has CERT record 6 0 0 FNIkyLB+Y6aUbaMuB8Wvd0pYUQta
+</pre>
+
+Again, gpg can be told to use that:
+
+<pre>
+$ echo foo | gpg --auto-key-locate cert --recipient cb@df7cb.de --encrypt -a
+gpg: key 58510B5A: public key "Christoph Berg " imported
+gpg: Total number processed: 1
+gpg: imported: 1
+gpg: automatically retrieved `cb@df7cb.de' via DNS CERT
+</pre>
+
+Thanks to weasel for some hints on using CERT.
+
+## SSHFP
+
+I'm also mentioning SSHFP records here since it fits in the topic - I have been
+using them for some months now:
+
+<pre>
+$ host -t SSHFP tesla.df7cb.de
+tesla.df7cb.de has SSHFP record 1 1 EE49B803541293656C33B86ECD781BD8F1D78AB5
+tesla.df7cb.de has SSHFP record 2 1 3E82FB5EE8AA0205305F0D0186F94D6FB3E0E744
+$ ssh -o 'VerifyHostKeyDNS yes' tesla.df7cb.de
+The authenticity of host 'tesla.df7cb.de (88.198.227.218)' can't be established.
+RSA key fingerprint is 5a:c9:38:ca:c0:2b:11:c1:c8:fb:f1:ad:73:a1:9c:8b.
+Matching host key fingerprint found in DNS.
+Are you sure you want to continue connecting (yes/no)?
+</pre>
+
+The records are generated with ssh-keygen -r.
+
+</div>
+
+</div>
+
+<div id="footer" class="pagefooter">
+
+<div id="pageinfo">
+
+<div class="tags">
+Tags:
+
+[debian](../tag/debian.html)
+
+</div>
+
+<div class="pagedate">
+Last edited <span class="date">Do 17 Feb 2011 13:21:52 CET</span>
+<!-- Created <span class="date">Do 01 Mär 2007 20:01:27 CET</span> -->
+</div>
+
+</div>
+
+<!-- from Christoph Berg's Blog -->
diff --git a/docs/_static/DETAILS.html b/docs/_static/DETAILS.html
new file mode 100644
index 0000000..7b0b9f8
--- /dev/null
+++ b/docs/_static/DETAILS.html
@@ -0,0 +1,2677 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+<title>GnuPG Details</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+<meta name="title" content="GnuPG Details"/>
+<meta name="generator" content="Org-mode"/>
+<meta name="generated" content="2013-07-03T09:52+0000"/>
+<meta name="author" content="isis"/>
+<meta name="description" content=""/>
+<meta name="keywords" content=""/>
+<style type="text/css">
+ <!--/*--><![CDATA[/*><!--*/
+ html { font-family: Times, serif; font-size: 12pt; }
+ .title { text-align: center; }
+ .todo { color: red; }
+ .done { color: green; }
+ .tag { background-color: #add8e6; font-weight:normal }
+ .target { }
+ .timestamp { color: #bebebe; }
+ .timestamp-kwd { color: #5f9ea0; }
+ .right {margin-left:auto; margin-right:0px; text-align:right;}
+ .left {margin-left:0px; margin-right:auto; text-align:left;}
+ .center {margin-left:auto; margin-right:auto; text-align:center;}
+ p.verse { margin-left: 3% }
+ pre {
+ border: 1pt solid #AEBDCC;
+ background-color: #F3F5F7;
+ padding: 5pt;
+ font-family: courier, monospace;
+ font-size: 90%;
+ overflow:auto;
+ }
+ table { border-collapse: collapse; }
+ td, th { vertical-align: top; }
+ th.right { text-align:center; }
+ th.left { text-align:center; }
+ th.center { text-align:center; }
+ td.right { text-align:right; }
+ td.left { text-align:left; }
+ td.center { text-align:center; }
+ dt { font-weight: bold; }
+ div.figure { padding: 0.5em; }
+ div.figure p { text-align: center; }
+ div.inlinetask {
+ padding:10px;
+ border:2px solid gray;
+ margin:10px;
+ background: #ffffcc;
+ }
+ textarea { overflow-x: auto; }
+ .linenr { font-size:smaller }
+ .code-highlighted {background-color:#ffff00;}
+ .org-info-js_info-navigation { border-style:none; }
+ #org-info-js_console-label { font-size:10px; font-weight:bold;
+ white-space:nowrap; }
+ .org-info-js_search-highlight {background-color:#ffff00; color:#000000;
+ font-weight:bold; }
+ /*]]>*/-->
+</style>
+<script type="text/javascript">
+/*
+@licstart The following is the entire license notice for the
+JavaScript code in this tag.
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+
+The JavaScript code in this tag is free software: you can
+redistribute it and/or modify it under the terms of the GNU
+General Public License (GNU GPL) as published by the Free Software
+Foundation, either version 3 of the License, or (at your option)
+any later version. The code is distributed WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
+
+As additional permission under GNU GPL version 3 section 7, you
+may distribute non-source (e.g., minimized or compacted) forms of
+that code without the copy of the GNU GPL normally required by
+section 4, provided you include this license notice and a URL
+through which recipients can access the Corresponding Source.
+
+
+@licend The above is the entire license notice
+for the JavaScript code in this tag.
+*/
+<!--/*--><![CDATA[/*><!--*/
+ function CodeHighlightOn(elem, id)
+ {
+ var target = document.getElementById(id);
+ if(null != target) {
+ elem.cacheClassElem = elem.className;
+ elem.cacheClassTarget = target.className;
+ target.className = "code-highlighted";
+ elem.className = "code-highlighted";
+ }
+ }
+ function CodeHighlightOff(elem, id)
+ {
+ var target = document.getElementById(id);
+ if(elem.cacheClassElem)
+ elem.className = elem.cacheClassElem;
+ if(elem.cacheClassTarget)
+ target.className = elem.cacheClassTarget;
+ }
+/*]]>*///-->
+</script>
+<script type="text/css" href="./agogo.css" />
+</head>
+<body>
+
+<div id="preamble">
+
+</div>
+
+<div id="content">
+<h1 class="title">GnuPG Details</h1>
+
+
+<p>
+This is the DETAILS file for GnuPG which specifies some internals and
+parts of the external API for GPG and GPGSM.
+</p>
+
+<div id="table-of-contents">
+<h2>Table of Contents</h2>
+<div id="text-table-of-contents">
+<ul>
+<li><a href="#sec-1">1 Format of the colon listings</a>
+<ul>
+<li><a href="#sec-1-1">1.1 Description of the fields</a>
+<ul>
+<li><a href="#sec-1-1-1">1.1.1 Field 1 - Type of record</a></li>
+<li><a href="#sec-1-1-2">1.1.2 Field 2 - Validity</a></li>
+<li><a href="#sec-1-1-3">1.1.3 Field 3 - Key length</a></li>
+<li><a href="#sec-1-1-4">1.1.4 Field 4 - Public key algorithm</a></li>
+<li><a href="#sec-1-1-5">1.1.5 Field 5 - KeyID</a></li>
+<li><a href="#sec-1-1-6">1.1.6 Field 6 - Creation date</a></li>
+<li><a href="#sec-1-1-7">1.1.7 Field 7 - Expiration date</a></li>
+<li><a href="#sec-1-1-8">1.1.8 Field 8 - Certificate S/N, UID hash, trust signature info</a></li>
+<li><a href="#sec-1-1-9">1.1.9 Field 9 - Ownertrust</a></li>
+<li><a href="#sec-1-1-10">1.1.10 Field 10 - User-ID</a></li>
+<li><a href="#sec-1-1-11">1.1.11 Field 11 - Signature class</a></li>
+<li><a href="#sec-1-1-12">1.1.12 Field 12 - Key capabilities</a></li>
+<li><a href="#sec-1-1-13">1.1.13 Field 13 - Issuer certificate fingerprint or other info</a></li>
+<li><a href="#sec-1-1-14">1.1.14 Field 14 - Flag field</a></li>
+<li><a href="#sec-1-1-15">1.1.15 Field 15 - S/N of a token</a></li>
+<li><a href="#sec-1-1-16">1.1.16 Field 16 - Hash algorithm</a></li>
+</ul>
+</li>
+<li><a href="#sec-1-2">1.2 Special fields</a>
+<ul>
+<li><a href="#sec-1-2-1">1.2.1 PKD - Public key data</a></li>
+<li><a href="#sec-1-2-2">1.2.2 TRU - Trust database information</a></li>
+<li><a href="#sec-1-2-3">1.2.3 SPK - Signature subpacket records</a></li>
+<li><a href="#sec-1-2-4">1.2.4 CFG - Configuration data</a></li>
+</ul></li>
+</ul>
+</li>
+<li><a href="#sec-2">2 Format of the &ndash;status-fd output</a>
+<ul>
+<li><a href="#sec-2-1">2.1 General status codes</a>
+<ul>
+<li><a href="#sec-2-1-1">2.1.1 NEWSIG</a></li>
+<li><a href="#sec-2-1-2">2.1.2 GOODSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</a></li>
+<li><a href="#sec-2-1-3">2.1.3 EXPSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</a></li>
+<li><a href="#sec-2-1-4">2.1.4 EXPKEYSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</a></li>
+<li><a href="#sec-2-1-5">2.1.5 REVKEYSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</a></li>
+<li><a href="#sec-2-1-6">2.1.6 BADSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</a></li>
+<li><a href="#sec-2-1-7">2.1.7 ERRSIG &lt;keyid&gt; &lt;pkalgo&gt; &lt;hashalgo&gt; &lt;sig_class&gt; &lt;time&gt; &lt;rc&gt;</a></li>
+<li><a href="#sec-2-1-8">2.1.8 VALIDSIG &lt;args&gt;</a></li>
+<li><a href="#sec-2-1-9">2.1.9 SIG_ID &lt;radix64_string&gt; &lt;sig_creation_date&gt; &lt;sig-timestamp&gt;</a></li>
+<li><a href="#sec-2-1-10">2.1.10 ENC_TO &lt;long_keyid&gt; &lt;keytype&gt; &lt;keylength&gt;</a></li>
+<li><a href="#sec-2-1-11">2.1.11 BEGIN_DECRYPTION</a></li>
+<li><a href="#sec-2-1-12">2.1.12 END_DECRYPTION</a></li>
+<li><a href="#sec-2-1-13">2.1.13 DECRYPTION_INFO &lt;mdc_method&gt; &lt;sym_algo&gt;</a></li>
+<li><a href="#sec-2-1-14">2.1.14 DECRYPTION_FAILED</a></li>
+<li><a href="#sec-2-1-15">2.1.15 DECRYPTION_OKAY</a></li>
+<li><a href="#sec-2-1-16">2.1.16 SESSION_KEY &lt;algo&gt;:&lt;hexdigits&gt;</a></li>
+<li><a href="#sec-2-1-17">2.1.17 BEGIN_ENCRYPTION &lt;mdc_method&gt; &lt;sym_algo&gt;</a></li>
+<li><a href="#sec-2-1-18">2.1.18 END_ENCRYPTION</a></li>
+<li><a href="#sec-2-1-19">2.1.19 FILE_START &lt;what&gt; &lt;filename&gt;</a></li>
+<li><a href="#sec-2-1-20">2.1.20 FILE_DONE</a></li>
+<li><a href="#sec-2-1-21">2.1.21 BEGIN_SIGNING</a></li>
+<li><a href="#sec-2-1-22">2.1.22 ALREADY_SIGNED &lt;long-keyid&gt;</a></li>
+<li><a href="#sec-2-1-23">2.1.23 SIG_CREATED &lt;type&gt; &lt;pk_algo&gt; &lt;hash_algo&gt; &lt;class&gt; &lt;timestamp&gt; &lt;keyfpr&gt;</a></li>
+<li><a href="#sec-2-1-24">2.1.24 NOTATION_</a></li>
+<li><a href="#sec-2-1-25">2.1.25 POLICY_URL &lt;string&gt;</a></li>
+<li><a href="#sec-2-1-26">2.1.26 PLAINTEXT &lt;format&gt; &lt;timestamp&gt; &lt;filename&gt;</a></li>
+<li><a href="#sec-2-1-27">2.1.27 PLAINTEXT_LENGTH &lt;length&gt;</a></li>
+<li><a href="#sec-2-1-28">2.1.28 ATTRIBUTE &lt;arguments&gt;</a></li>
+<li><a href="#sec-2-1-29">2.1.29 SIG_SUBPACKET &lt;type&gt; &lt;flags&gt; &lt;len&gt; &lt;data&gt;</a></li>
+</ul>
+</li>
+<li><a href="#sec-2-2">2.2 Key related</a>
+<ul>
+<li><a href="#sec-2-2-1">2.2.1 INV_RECP, INV_SGNR</a></li>
+<li><a href="#sec-2-2-2">2.2.2 NO_RECP &lt;reserved&gt;</a></li>
+<li><a href="#sec-2-2-3">2.2.3 NO_SGNR &lt;reserved&gt;</a></li>
+<li><a href="#sec-2-2-4">2.2.4 KEYEXPIRED &lt;expire-timestamp&gt;</a></li>
+<li><a href="#sec-2-2-5">2.2.5 KEYREVOKED</a></li>
+<li><a href="#sec-2-2-6">2.2.6 NO_PUBKEY &lt;long keyid&gt;</a></li>
+<li><a href="#sec-2-2-7">2.2.7 NO_SECKEY &lt;long keyid&gt;</a></li>
+<li><a href="#sec-2-2-8">2.2.8 KEY_CREATED &lt;type&gt; &lt;fingerprint&gt; [&lt;handle&gt;]</a></li>
+<li><a href="#sec-2-2-9">2.2.9 KEY_NOT_CREATED [&lt;handle&gt;]</a></li>
+<li><a href="#sec-2-2-10">2.2.10 TRUST_</a></li>
+<li><a href="#sec-2-2-11">2.2.11 PKA_TRUST_</a></li>
+</ul>
+</li>
+<li><a href="#sec-2-3">2.3 Remote control</a>
+<ul>
+<li><a href="#sec-2-3-1">2.3.1 GET_BOOL, GET_LINE, GET_HIDDEN, GOT_IT</a></li>
+<li><a href="#sec-2-3-2">2.3.2 USERID_HINT &lt;long main keyid&gt; &lt;string&gt;</a></li>
+<li><a href="#sec-2-3-3">2.3.3 NEED_PASSPHRASE &lt;long keyid&gt; &lt;long main keyid&gt; &lt;keytype&gt; &lt;keylength&gt;</a></li>
+<li><a href="#sec-2-3-4">2.3.4 NEED_PASSPHRASE_SYM &lt;cipher_algo&gt; &lt;s2k_mode&gt; &lt;s2k_hash&gt;</a></li>
+<li><a href="#sec-2-3-5">2.3.5 NEED_PASSPHRASE_PIN &lt;card_type&gt; &lt;chvno&gt; [&lt;serialno&gt;]</a></li>
+<li><a href="#sec-2-3-6">2.3.6 MISSING_PASSPHRASE</a></li>
+<li><a href="#sec-2-3-7">2.3.7 BAD_PASSPHRASE &lt;long keyid&gt;</a></li>
+<li><a href="#sec-2-3-8">2.3.8 GOOD_PASSPHRASE</a></li>
+</ul>
+</li>
+<li><a href="#sec-2-4">2.4 Import/Export</a>
+<ul>
+<li><a href="#sec-2-4-1">2.4.1 IMPORT_CHECK &lt;long keyid&gt; &lt;fingerprint&gt; &lt;user ID&gt;</a></li>
+<li><a href="#sec-2-4-2">2.4.2 IMPORTED &lt;long keyid&gt; &lt;username&gt;</a></li>
+<li><a href="#sec-2-4-3">2.4.3 IMPORT_OK &lt;reason&gt; [&lt;fingerprint&gt;]</a></li>
+<li><a href="#sec-2-4-4">2.4.4 IMPORT_PROBLEM &lt;reason&gt; [&lt;fingerprint&gt;]</a></li>
+<li><a href="#sec-2-4-5">2.4.5 IMPORT_RES &lt;args&gt;</a></li>
+</ul>
+</li>
+<li><a href="#sec-2-5">2.5 Smartcard related</a>
+<ul>
+<li><a href="#sec-2-5-1">2.5.1 CARDCTRL &lt;what&gt; [&lt;serialno&gt;]</a></li>
+<li><a href="#sec-2-5-2">2.5.2 SC_OP_FAILURE [&lt;code&gt;]</a></li>
+<li><a href="#sec-2-5-3">2.5.3 SC_OP_SUCCESS</a></li>
+</ul>
+</li>
+<li><a href="#sec-2-6">2.6 Miscellaneous status codes</a>
+<ul>
+<li><a href="#sec-2-6-1">2.6.1 NODATA &lt;what&gt;</a></li>
+<li><a href="#sec-2-6-2">2.6.2 UNEXPECTED &lt;what&gt;</a></li>
+<li><a href="#sec-2-6-3">2.6.3 TRUNCATED &lt;maxno&gt;</a></li>
+<li><a href="#sec-2-6-4">2.6.4 ERROR &lt;error location&gt; &lt;error code&gt; [&lt;more&gt;]</a></li>
+<li><a href="#sec-2-6-5">2.6.5 SUCCESS [&lt;location&gt;]</a></li>
+<li><a href="#sec-2-6-6">2.6.6 BADARMOR</a></li>
+<li><a href="#sec-2-6-7">2.6.7 DELETE_PROBLEM &lt;reason_code&gt;</a></li>
+<li><a href="#sec-2-6-8">2.6.8 PROGRESS &lt;what&gt; &lt;char&gt; &lt;cur&gt; &lt;total&gt;</a></li>
+<li><a href="#sec-2-6-9">2.6.9 BACKUP_KEY_CREATED &lt;fingerprint&gt; &lt;fname&gt;</a></li>
+<li><a href="#sec-2-6-10">2.6.10 MOUNTPOINT &lt;name&gt;</a></li>
+<li><a href="#sec-2-6-11">2.6.11 PINENTRY_LAUNCHED &lt;pid&gt;</a></li>
+</ul>
+</li>
+<li><a href="#sec-2-7">2.7 Obsolete status codes</a>
+<ul>
+<li><a href="#sec-2-7-1">2.7.1 SIGEXPIRED</a></li>
+<li><a href="#sec-2-7-2">2.7.2 RSA_OR_IDEA</a></li>
+<li><a href="#sec-2-7-3">2.7.3 SHM_INFO, SHM_GET, SHM_GET_BOOL, SHM_GET_HIDDEN</a></li>
+<li><a href="#sec-2-7-4">2.7.4 BEGIN_STREAM, END_STREAM</a></li>
+</ul></li>
+</ul>
+</li>
+<li><a href="#sec-3">3 Format of the &ndash;attribute-fd output</a></li>
+<li><a href="#sec-4">4 Unattended key generation</a></li>
+<li><a href="#sec-5">5 Layout of the TrustDB</a></li>
+<li><a href="#sec-6">6 GNU extensions to the S2K algorithm</a></li>
+<li><a href="#sec-7">7 Keyserver helper message format</a></li>
+<li><a href="#sec-8">8 Object identifiers</a></li>
+<li><a href="#sec-9">9 Miscellaneous notes</a>
+<ul>
+<li><a href="#sec-9-1">9.1 v3 fingerprints</a></li>
+<li><a href="#sec-9-2">9.2 Simplified revocation certificates</a></li>
+<li><a href="#sec-9-3">9.3 Documentation on HKP (the http keyserver protocol):</a></li>
+</ul>
+</li>
+</ul>
+</div>
+</div>
+
+<div id="outline-container-1" class="outline-2">
+<h2 id="sec-1"><span class="section-number-2">1</span> Format of the colon listings</h2>
+<div class="outline-text-2" id="text-1">
+
+<p> The format is a based on colon separated record, each recods starts
+ with a tag string and extends to the end of the line. Here is an
+ example:
+</p>
+
+
+<pre class="example">$ gpg --with-colons --list-keys \
+ --with-fingerprint --with-fingerprint wk@gnupg.org
+pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
+fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:
+uid:f::::::::Werner Koch &lt;wk@g10code.com&gt;:
+uid:f::::::::Werner Koch &lt;wk@gnupg.org&gt;:
+sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e:
+fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1:
+sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc:
+fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4:
+</pre>
+
+
+<p>
+The double <code>--with-fingerprint</code> prints the fingerprint for the subkeys
+too. Old versions of gpg used a lighly different format and required
+the use of the option <code>--fixed-list-mode</code> to conform to format
+described here.
+</p>
+
+</div>
+
+<div id="outline-container-1-1" class="outline-3">
+<h3 id="sec-1-1"><span class="section-number-3">1.1</span> Description of the fields</h3>
+<div class="outline-text-3" id="text-1-1">
+
+
+</div>
+
+<div id="outline-container-1-1-1" class="outline-4">
+<h4 id="sec-1-1-1"><span class="section-number-4">1.1.1</span> Field 1 - Type of record</h4>
+<div class="outline-text-4" id="text-1-1-1">
+
+
+<dl>
+<dt>pub</dt><dd>Public key
+</dd>
+<dt>crt</dt><dd>X.509 certificate
+</dd>
+<dt>crs</dt><dd>X.509 certificate and private key available
+</dd>
+<dt>sub</dt><dd>Subkey (secondary key)
+</dd>
+<dt>sec</dt><dd>Secret key
+</dd>
+<dt>ssb</dt><dd>Secret subkey (secondary key)
+</dd>
+<dt>uid</dt><dd>User id (only field 10 is used).
+</dd>
+<dt>uat</dt><dd>User attribute (same as user id except for field 10).
+</dd>
+<dt>sig</dt><dd>Signature
+</dd>
+<dt>rev</dt><dd>Revocation signature
+</dd>
+<dt>fpr</dt><dd>Fingerprint (fingerprint is in field 10)
+</dd>
+<dt>pkd</dt><dd>Public key data [*]
+</dd>
+<dt>grp</dt><dd>Keygrip
+</dd>
+<dt>rvk</dt><dd>Revocation key
+</dd>
+<dt>tru</dt><dd>Trust database information [*]
+</dd>
+<dt>spk</dt><dd>Signature subpacket [*]
+</dd>
+<dt>cfg</dt><dd>Configuration data [*]
+</dd>
+</dl>
+
+
+<p>
+ Records marked with an asterisk are described at <a href="#Special-field-formats">*Special fields</a>.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-2" class="outline-4">
+<h4 id="sec-1-1-2"><span class="section-number-4">1.1.2</span> Field 2 - Validity</h4>
+<div class="outline-text-4" id="text-1-1-2">
+
+
+<p>
+ This is a letter describing the computed validity of a key.
+ Currently this is a single letter, but be prepared that additional
+ information may follow in some future versions. Note that GnuPG &lt;
+ 2.1 does not set this field for secret key listings.
+</p>
+<dl>
+<dt>o</dt><dd>Unknown (this key is new to the system)
+</dd>
+<dt>i</dt><dd>The key is invalid (e.g. due to a missing self-signature)
+</dd>
+<dt>d</dt><dd>The key has been disabled
+ (deprecated - use the 'D' in field 12 instead)
+</dd>
+<dt>r</dt><dd>The key has been revoked
+</dd>
+<dt>e</dt><dd>The key has expired
+</dd>
+<dt>-</dt><dd>Unknown validity (i.e. no value assigned)
+</dd>
+<dt>q</dt><dd>Undefined validity. '-' and 'q' may safely be treated as
+ the same value for most purposes
+</dd>
+<dt>n</dt><dd>The key is not valid
+</dd>
+<dt>m</dt><dd>The key is marginal valid.
+</dd>
+<dt>f</dt><dd>The key is fully valid
+</dd>
+<dt>u</dt><dd>The key is ultimately valid. This often means that the
+ secret key is available, but any key may be marked as
+ ultimately valid.
+</dd>
+<dt>w</dt><dd>The key has a well known private part.
+</dd>
+<dt>s</dt><dd>The key has special validity. This means that it might be
+ self-signed and expected to be used in the STEED sytem.
+</dd>
+</dl>
+
+
+<p>
+ If the validity information is given for a UID or UAT record, it
+ describes the validity calculated based on this user ID. If given
+ for a key record it describes the validity taken from the best
+ rated user ID.
+</p>
+<p>
+ For X.509 certificates a 'u' is used for a trusted root
+ certificate (i.e. for the trust anchor) and an 'f' for all other
+ valid certificates.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-3" class="outline-4">
+<h4 id="sec-1-1-3"><span class="section-number-4">1.1.3</span> Field 3 - Key length</h4>
+<div class="outline-text-4" id="text-1-1-3">
+
+
+<p>
+ The length of key in bits.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-4" class="outline-4">
+<h4 id="sec-1-1-4"><span class="section-number-4">1.1.4</span> Field 4 - Public key algorithm</h4>
+<div class="outline-text-4" id="text-1-1-4">
+
+
+<p>
+ The values here are those from the OpenPGP specs or if they are
+ greather than 255 the algorithm ids as used by Libgcrypt.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-5" class="outline-4">
+<h4 id="sec-1-1-5"><span class="section-number-4">1.1.5</span> Field 5 - KeyID</h4>
+<div class="outline-text-4" id="text-1-1-5">
+
+
+<p>
+ This is the 64 bit keyid as specified by OpenPGP and the last 64
+ bit of the SHA-1 fingerprint of an X.509 certifciate.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-6" class="outline-4">
+<h4 id="sec-1-1-6"><span class="section-number-4">1.1.6</span> Field 6 - Creation date</h4>
+<div class="outline-text-4" id="text-1-1-6">
+
+
+<p>
+ The creation date of the key is given in UTC. For UID and UAT
+ records, this is used for the self-signature date. Note that the
+ date is usally printed in seconds since epoch, however, we are
+ migrating to an ISO 8601 format (e.g. "19660205T091500"). This is
+ currently only relevant for X.509. A simple way to detect the new
+ format is to scan for the 'T'. Note that old versions of gpg
+ without using the <code>--fixed-list-mode</code> option used a "yyyy-mm-tt"
+ format.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-7" class="outline-4">
+<h4 id="sec-1-1-7"><span class="section-number-4">1.1.7</span> Field 7 - Expiration date</h4>
+<div class="outline-text-4" id="text-1-1-7">
+
+
+<p>
+ Key or UID/UAT expiration date or empty if it does not expire.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-8" class="outline-4">
+<h4 id="sec-1-1-8"><span class="section-number-4">1.1.8</span> Field 8 - Certificate S/N, UID hash, trust signature info</h4>
+<div class="outline-text-4" id="text-1-1-8">
+
+
+<p>
+ Used for serial number in crt records. For UID and UAT records,
+ this is a hash of the user ID contents used to represent that
+ exact user ID. For trust signatures, this is the trust depth
+ seperated by the trust value by a space.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-9" class="outline-4">
+<h4 id="sec-1-1-9"><span class="section-number-4">1.1.9</span> Field 9 - Ownertrust</h4>
+<div class="outline-text-4" id="text-1-1-9">
+
+
+<p>
+ This is only used on primary keys. This is a single letter, but
+ be prepared that additional information may follow in future
+ versions. For trust signatures with a regular expression, this is
+ the regular expression value, quoted as in field 10.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-10" class="outline-4">
+<h4 id="sec-1-1-10"><span class="section-number-4">1.1.10</span> Field 10 - User-ID</h4>
+<div class="outline-text-4" id="text-1-1-10">
+
+<p> The value is quoted like a C string to avoid control characters
+ (the colon is quoted <code>\x3a</code>). For a "pub" record this field is
+ not used on &ndash;fixed-list-mode. A UAT record puts the attribute
+ subpacket count here, a space, and then the total attribute
+ subpacket size. In gpgsm the issuer name comes here. A FPR
+ record stores the fingerprint here. The fingerprint of a
+ revocation key is stored here.
+</p></div>
+
+</div>
+
+<div id="outline-container-1-1-11" class="outline-4">
+<h4 id="sec-1-1-11"><span class="section-number-4">1.1.11</span> Field 11 - Signature class</h4>
+<div class="outline-text-4" id="text-1-1-11">
+
+
+<p>
+ Signature class as per RFC-4880. This is a 2 digit hexnumber
+ followed by either the letter 'x' for an exportable signature or
+ the letter 'l' for a local-only signature. The class byte of an
+ revocation key is also given here, 'x' and 'l' is used the same
+ way. This field if not used for X.509.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-12" class="outline-4">
+<h4 id="sec-1-1-12"><span class="section-number-4">1.1.12</span> Field 12 - Key capabilities</h4>
+<div class="outline-text-4" id="text-1-1-12">
+
+
+<p>
+ The defined capabilities are:
+</p>
+<dl>
+<dt>e</dt><dd>Encrypt
+</dd>
+<dt>s</dt><dd>Sign
+</dd>
+<dt>c</dt><dd>Certify
+</dd>
+<dt>a</dt><dd>Authentication
+</dd>
+<dt>?</dt><dd>Unknown capability
+</dd>
+</dl>
+
+
+<p>
+ A key may have any combination of them in any order. In addition
+ to these letters, the primary key has uppercase versions of the
+ letters to denote the <span style="text-decoration:underline;">usable</span> capabilities of the entire key, and
+ a potential letter 'D' to indicate a disabled key.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-13" class="outline-4">
+<h4 id="sec-1-1-13"><span class="section-number-4">1.1.13</span> Field 13 - Issuer certificate fingerprint or other info</h4>
+<div class="outline-text-4" id="text-1-1-13">
+
+
+<p>
+ Used in FPR records for S/MIME keys to store the fingerprint of
+ the issuer certificate. This is useful to build the certificate
+ path based on certificates stored in the local key database it is
+ only filled if the issuer certificate is available. The root has
+ been reached if this is the same string as the fingerprint. The
+ advantage of using this value is that it is guaranteed to have
+ been been build by the same lookup algorithm as gpgsm uses.
+</p>
+<p>
+ For "uid" records this field lists the preferences in the same way
+ gpg's &ndash;edit-key menu does.
+</p>
+<p>
+ For "sig" records, this is the fingerprint of the key that issued
+ the signature. Note that this is only filled in if the signature
+ verified correctly. Note also that for various technical reasons,
+ this fingerprint is only available if &ndash;no-sig-cache is used.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-14" class="outline-4">
+<h4 id="sec-1-1-14"><span class="section-number-4">1.1.14</span> Field 14 - Flag field</h4>
+<div class="outline-text-4" id="text-1-1-14">
+
+
+<p>
+ Flag field used in the &ndash;edit menu output
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-15" class="outline-4">
+<h4 id="sec-1-1-15"><span class="section-number-4">1.1.15</span> Field 15 - S/N of a token</h4>
+<div class="outline-text-4" id="text-1-1-15">
+
+
+<p>
+ Used in sec/sbb to print the serial number of a token (internal
+ protect mode 1002) or a '#' if that key is a simple stub (internal
+ protect mode 1001)
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-1-1-16" class="outline-4">
+<h4 id="sec-1-1-16"><span class="section-number-4">1.1.16</span> Field 16 - Hash algorithm</h4>
+<div class="outline-text-4" id="text-1-1-16">
+
+
+<p>
+ For sig records, this is the used hash algorithm. For example:
+ 2 = SHA-1, 8 = SHA-256.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-1-2" class="outline-3">
+<h3 id="sec-1-2"><span class="section-number-3">1.2</span> Special fields</h3>
+<div class="outline-text-3" id="text-1-2">
+
+
+
+</div>
+
+<div id="outline-container-1-2-1" class="outline-4">
+<h4 id="sec-1-2-1"><span class="section-number-4">1.2.1</span> PKD - Public key data</h4>
+<div class="outline-text-4" id="text-1-2-1">
+
+
+<p>
+ If field 1 has the tag "pkd", a listing looks like this:
+</p>
+
+
+<pre class="example">pkd:0:1024:B665B1435F4C2 .... FF26ABB:
+ ! ! !-- the value
+ ! !------ for information number of bits in the value
+ !--------- index (eg. DSA goes from 0 to 3: p,q,g,y)
+</pre>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-1-2-2" class="outline-4">
+<h4 id="sec-1-2-2"><span class="section-number-4">1.2.2</span> TRU - Trust database information</h4>
+<div class="outline-text-4" id="text-1-2-2">
+
+<p> Example for a "tru" trust base record:
+</p>
+
+
+<pre class="example">tru:o:0:1166697654:1:3:1:5
+</pre>
+
+
+<dl>
+<dt>Field 2</dt><dd>Reason for staleness of trust. If this field is
+ empty, then the trustdb is not stale. This field may
+ have multiple flags in it:
+
+<dl>
+<dt>o</dt><dd>Trustdb is old
+</dd>
+<dt>t</dt><dd>Trustdb was built with a different trust model
+ than the one we are using now.
+
+</dd>
+</dl>
+
+</dd>
+<dt>Field 3</dt><dd>Trust model
+
+<dl>
+<dt>0</dt><dd>Classic trust model, as used in PGP 2.x.
+</dd>
+<dt>1</dt><dd>PGP trust model, as used in PGP 6 and later.
+ This is the same as the classic trust model,
+ except for the addition of trust signatures.
+
+</dd>
+</dl>
+
+<p> GnuPG before version 1.4 used the classic trust model
+ by default. GnuPG 1.4 and later uses the PGP trust
+ model by default.
+</p>
+</dd>
+<dt>Field 4</dt><dd>Date trustdb was created in seconds since Epoch.
+</dd>
+<dt>Field 5</dt><dd>Date trustdb will expire in seconds since Epoch.
+</dd>
+<dt>Field 6</dt><dd>Number of marginally trusted users to introduce a new
+ key signer (gpg's option &ndash;marginals-needed).
+</dd>
+<dt>Field 7</dt><dd>Number of completely trusted users to introduce a new
+ key signer. (gpg's option &ndash;completes-needed)
+
+</dd>
+<dt>Field 8</dt><dd>Maximum depth of a certification chain. (gpg's option
+ &ndash;max-cert-depth)
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-1-2-3" class="outline-4">
+<h4 id="sec-1-2-3"><span class="section-number-4">1.2.3</span> SPK - Signature subpacket records</h4>
+<div class="outline-text-4" id="text-1-2-3">
+
+
+<dl>
+<dt>Field 2</dt><dd>Subpacket number as per RFC-4880 and later.
+</dd>
+<dt>Field 3</dt><dd>Flags in hex. Currently the only two bits assigned
+ are 1, to indicate that the subpacket came from the
+ hashed part of the signature, and 2, to indicate the
+ subpacket was marked critical.
+</dd>
+<dt>Field 4</dt><dd>Length of the subpacket. Note that this is the
+ length of the subpacket, and not the length of field
+ 5 below. Due to the need for %-encoding, the length
+ of field 5 may be up to 3x this value.
+</dd>
+<dt>Field 5</dt><dd>The subpacket data. Printable ASCII is shown as
+ ASCII, but other values are rendered as %XX where XX
+ is the hex value for the byte.
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-1-2-4" class="outline-4">
+<h4 id="sec-1-2-4"><span class="section-number-4">1.2.4</span> CFG - Configuration data</h4>
+<div class="outline-text-4" id="text-1-2-4">
+
+
+<p>
+ &ndash;list-config outputs information about the GnuPG configuration
+ for the benefit of frontends or other programs that call GnuPG.
+ There are several list-config items, all colon delimited like the
+ rest of the &ndash;with-colons output. The first field is always "cfg"
+ to indicate configuration information. The second field is one of
+ (with examples):
+</p>
+<dl>
+<dt>version</dt><dd>The third field contains the version of GnuPG.
+
+<pre class="example">
+cfg:version:1.3.5
+</pre>
+
+
+</dd>
+<dt>pubkey</dt><dd>The third field contains the public key algorithms
+ this version of GnuPG supports, separated by
+ semicolons. The algorithm numbers are as specified in
+ RFC-4880. Note that in contrast to the &ndash;status-fd
+ interface these are <span style="text-decoration:underline;">not</span> the Libgcrypt identifiers.
+
+<pre class="example">
+cfg:pubkey:1;2;3;16;17
+</pre>
+
+
+</dd>
+<dt>cipher</dt><dd>The third field contains the symmetric ciphers this
+ version of GnuPG supports, separated by semicolons.
+ The cipher numbers are as specified in RFC-4880.
+
+<pre class="example">
+cfg:cipher:2;3;4;7;8;9;10
+</pre>
+
+
+</dd>
+<dt>digest</dt><dd>The third field contains the digest (hash) algorithms
+ this version of GnuPG supports, separated by
+ semicolons. The digest numbers are as specified in
+ RFC-4880.
+
+<pre class="example">
+cfg:digest:1;2;3;8;9;10
+</pre>
+
+
+</dd>
+<dt>compress</dt><dd>The third field contains the compression algorithms
+ this version of GnuPG supports, separated by
+ semicolons. The algorithm numbers are as specified
+ in RFC-4880.
+
+<pre class="example">
+cfg:compress:0;1;2;3
+</pre>
+
+
+</dd>
+<dt>group</dt><dd>The third field contains the name of the group, and the
+ fourth field contains the values that the group expands
+ to, separated by semicolons.
+
+<p>
+ For example, a group of:
+</p><pre class="example">
+group mynames = paige 0x12345678 joe patti
+</pre>
+
+<p> would result in:
+</p><pre class="example">
+cfg:group:mynames:patti;joe;0x12345678;paige
+</pre>
+
+</dd>
+</dl>
+
+
+
+</div>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2" class="outline-2">
+<h2 id="sec-2"><span class="section-number-2">2</span> Format of the &ndash;status-fd output</h2>
+<div class="outline-text-2" id="text-2">
+
+
+<p>
+ Every line is prefixed with "[GNUPG:] ", followed by a keyword with
+ the type of the status line and some arguments depending on the type
+ (maybe none); an application should always be prepared to see more
+ arguments in future versions.
+</p>
+
+</div>
+
+<div id="outline-container-2-1" class="outline-3">
+<h3 id="sec-2-1"><span class="section-number-3">2.1</span> General status codes</h3>
+<div class="outline-text-3" id="text-2-1">
+
+
+</div>
+
+<div id="outline-container-2-1-1" class="outline-4">
+<h4 id="sec-2-1-1"><span class="section-number-4">2.1.1</span> NEWSIG</h4>
+<div class="outline-text-4" id="text-2-1-1">
+
+<p> May be issued right before a signature verification starts. This
+ is useful to define a context for parsing ERROR status messages.
+ No arguments are currently defined.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-2" class="outline-4">
+<h4 id="sec-2-1-2"><span class="section-number-4">2.1.2</span> GOODSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</h4>
+<div class="outline-text-4" id="text-2-1-2">
+
+<p> The signature with the keyid is good. For each signature only one
+ of the codes GOODSIG, BADSIG, EXPSIG, EXPKEYSIG, REVKEYSIG or
+ ERRSIG will be emitted. In the past they were used as a marker
+ for a new signature; new code should use the NEWSIG status
+ instead. The username is the primary one encoded in UTF-8 and %XX
+ escaped. The fingerprint may be used instead of the long keyid if
+ it is available. This is the case with CMS and might eventually
+ also be available for OpenPGP.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-3" class="outline-4">
+<h4 id="sec-2-1-3"><span class="section-number-4">2.1.3</span> EXPSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</h4>
+<div class="outline-text-4" id="text-2-1-3">
+
+<p> The signature with the keyid is good, but the signature is
+ expired. The username is the primary one encoded in UTF-8 and %XX
+ escaped. The fingerprint may be used instead of the long keyid if
+ it is available. This is the case with CMS and might eventually
+ also be available for OpenPGP.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-4" class="outline-4">
+<h4 id="sec-2-1-4"><span class="section-number-4">2.1.4</span> EXPKEYSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</h4>
+<div class="outline-text-4" id="text-2-1-4">
+
+<p> The signature with the keyid is good, but the signature was made
+ by an expired key. The username is the primary one encoded in
+ UTF-8 and %XX escaped. The fingerprint may be used instead of the
+ long keyid if it is available. This is the case with CMS and
+ might eventually also be available for OpenPGP.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-5" class="outline-4">
+<h4 id="sec-2-1-5"><span class="section-number-4">2.1.5</span> REVKEYSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</h4>
+<div class="outline-text-4" id="text-2-1-5">
+
+<p> The signature with the keyid is good, but the signature was made
+ by a revoked key. The username is the primary one encoded in UTF-8
+ and %XX escaped. The fingerprint may be used instead of the long
+ keyid if it is available. This is the case with CMS and might
+ eventually also beñ available for OpenPGP.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-6" class="outline-4">
+<h4 id="sec-2-1-6"><span class="section-number-4">2.1.6</span> BADSIG &lt;long_keyid_or_fpr&gt; &lt;username&gt;</h4>
+<div class="outline-text-4" id="text-2-1-6">
+
+<p> The signature with the keyid has not been verified okay. The
+ username is the primary one encoded in UTF-8 and %XX escaped. The
+ fingerprint may be used instead of the long keyid if it is
+ available. This is the case with CMS and might eventually also be
+ available for OpenPGP.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-7" class="outline-4">
+<h4 id="sec-2-1-7"><span class="section-number-4">2.1.7</span> ERRSIG &lt;keyid&gt; &lt;pkalgo&gt; &lt;hashalgo&gt; &lt;sig_class&gt; &lt;time&gt; &lt;rc&gt;</h4>
+<div class="outline-text-4" id="text-2-1-7">
+
+<p> It was not possible to check the signature. This may be caused by
+ a missing public key or an unsupported algorithm. A RC of 4
+ indicates unknown algorithm, a 9 indicates a missing public
+ key. The other fields give more information about this signature.
+ sig_class is a 2 byte hex-value. The fingerprint may be used
+ instead of the keyid if it is available. This is the case with
+ gpgsm and might eventually also be available for OpenPGP.
+</p>
+<p>
+ Note, that TIME may either be the number of seconds since Epoch or
+ the letter 'T'.
+ an ISO 8601 string. The latter can be detected by the presence of
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-8" class="outline-4">
+<h4 id="sec-2-1-8"><span class="section-number-4">2.1.8</span> VALIDSIG &lt;args&gt;</h4>
+<div class="outline-text-4" id="text-2-1-8">
+
+
+<p>
+ The args are:
+</p>
+<ul>
+<li>&lt;fingerprint_in_hex&gt;
+</li>
+<li>&lt;sig_creation_date&gt;
+</li>
+<li>&lt;sig-timestamp&gt;
+</li>
+<li>&lt;expire-timestamp&gt;
+</li>
+<li>&lt;sig-version&gt;
+</li>
+<li>&lt;reserved&gt;
+</li>
+<li>&lt;pubkey-algo&gt;
+</li>
+<li>&lt;hash-algo&gt;
+</li>
+<li>&lt;sig-class&gt;
+</li>
+<li>[ &lt;primary-key-fpr&gt; ]
+</li>
+</ul>
+
+
+<p>
+ This status indicates that the signature is good. This is the same
+ as GOODSIG but has the fingerprint as the argument. Both status
+ lines are emitted for a good signature. All arguments here are on
+ one long line. sig-timestamp is the signature creation time in
+ seconds after the epoch. expire-timestamp is the signature
+ expiration time in seconds after the epoch (zero means "does not
+ expire"). sig-version, pubkey-algo, hash-algo, and sig-class (a
+ 2-byte hex value) are all straight from the signature packet.
+ PRIMARY-KEY-FPR is the fingerprint of the primary key or identical
+ to the first argument. This is useful to get back to the primary
+ key without running gpg again for this purpose.
+</p>
+<p>
+ The primary-key-fpr parameter is used for OpenPGP and not
+ class is not defined for CMS and currently set to 0 and 00.
+ available for CMS signatures. The sig-version as well as the sig
+</p>
+<p>
+ Note, that *-TIMESTAMP may either be a number of seconds since
+ Epoch or an ISO 8601 string which can be detected by the presence
+ of the letter 'T'.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-9" class="outline-4">
+<h4 id="sec-2-1-9"><span class="section-number-4">2.1.9</span> SIG_ID &lt;radix64_string&gt; &lt;sig_creation_date&gt; &lt;sig-timestamp&gt;</h4>
+<div class="outline-text-4" id="text-2-1-9">
+
+<p> This is emitted only for signatures of class 0 or 1 which have
+ been verified okay. The string is a signature id and may be used
+ in applications to detect replay attacks of signed messages. Note
+ that only DLP algorithms give unique ids - others may yield
+ duplicated ones when they have been created in the same second.
+</p>
+<p>
+ Note, that SIG-TIMESTAMP may either be a number of seconds since
+ Epoch or an ISO 8601 string which can be detected by the presence
+ of the letter 'T'.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-10" class="outline-4">
+<h4 id="sec-2-1-10"><span class="section-number-4">2.1.10</span> ENC_TO &lt;long_keyid&gt; &lt;keytype&gt; &lt;keylength&gt;</h4>
+<div class="outline-text-4" id="text-2-1-10">
+
+<p> The message is encrypted to this LONG_KEYID. KEYTYPE is the
+ numerical value of the public key algorithm or 0 if it is not
+ known, KEYLENGTH is the length of the key or 0 if it is not known
+ (which is currently always the case). Gpg prints this line
+ always; Gpgsm only if it knows the certificate.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-11" class="outline-4">
+<h4 id="sec-2-1-11"><span class="section-number-4">2.1.11</span> BEGIN_DECRYPTION</h4>
+<div class="outline-text-4" id="text-2-1-11">
+
+<p> Mark the start of the actual decryption process. This is also
+ emitted when in &ndash;list-only mode.
+</p></div>
+
+</div>
+
+<div id="outline-container-2-1-12" class="outline-4">
+<h4 id="sec-2-1-12"><span class="section-number-4">2.1.12</span> END_DECRYPTION</h4>
+<div class="outline-text-4" id="text-2-1-12">
+
+<p> Mark the end of the actual decryption process. This are also
+ emitted when in &ndash;list-only mode.
+</p></div>
+
+</div>
+
+<div id="outline-container-2-1-13" class="outline-4">
+<h4 id="sec-2-1-13"><span class="section-number-4">2.1.13</span> DECRYPTION_INFO &lt;mdc_method&gt; &lt;sym_algo&gt;</h4>
+<div class="outline-text-4" id="text-2-1-13">
+
+<p> Print information about the symmetric encryption algorithm and the
+ MDC method. This will be emitted even if the decryption fails.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-14" class="outline-4">
+<h4 id="sec-2-1-14"><span class="section-number-4">2.1.14</span> DECRYPTION_FAILED</h4>
+<div class="outline-text-4" id="text-2-1-14">
+
+<p> The symmetric decryption failed - one reason could be a wrong
+ passphrase for a symmetrical encrypted message.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-15" class="outline-4">
+<h4 id="sec-2-1-15"><span class="section-number-4">2.1.15</span> DECRYPTION_OKAY</h4>
+<div class="outline-text-4" id="text-2-1-15">
+
+<p> The decryption process succeeded. This means, that either the
+ correct secret key has been used or the correct passphrase for a
+ conventional encrypted message was given. The program itself may
+ return an errorcode because it may not be possible to verify a
+ signature for some reasons.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-16" class="outline-4">
+<h4 id="sec-2-1-16"><span class="section-number-4">2.1.16</span> SESSION_KEY &lt;algo&gt;:&lt;hexdigits&gt;</h4>
+<div class="outline-text-4" id="text-2-1-16">
+
+<p> The session key used to decrypt the message. This message will
+ only be emitted when the special option &ndash;show-session-key is
+ used. The format is suitable to be passed to the option
+ &ndash;override-session-key
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-17" class="outline-4">
+<h4 id="sec-2-1-17"><span class="section-number-4">2.1.17</span> BEGIN_ENCRYPTION &lt;mdc_method&gt; &lt;sym_algo&gt;</h4>
+<div class="outline-text-4" id="text-2-1-17">
+
+<p> Mark the start of the actual encryption process.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-18" class="outline-4">
+<h4 id="sec-2-1-18"><span class="section-number-4">2.1.18</span> END_ENCRYPTION</h4>
+<div class="outline-text-4" id="text-2-1-18">
+
+<p> Mark the end of the actual encryption process.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-19" class="outline-4">
+<h4 id="sec-2-1-19"><span class="section-number-4">2.1.19</span> FILE_START &lt;what&gt; &lt;filename&gt;</h4>
+<div class="outline-text-4" id="text-2-1-19">
+
+<p> Start processing a file &lt;filename&gt;. &lt;what&gt; indicates the performed
+ operation:
+</p><dl>
+<dt>1</dt><dd>verify
+</dd>
+<dt>2</dt><dd>encrypt
+</dd>
+<dt>3</dt><dd>decrypt
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-1-20" class="outline-4">
+<h4 id="sec-2-1-20"><span class="section-number-4">2.1.20</span> FILE_DONE</h4>
+<div class="outline-text-4" id="text-2-1-20">
+
+<p> Marks the end of a file processing which has been started
+ by FILE_START.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-21" class="outline-4">
+<h4 id="sec-2-1-21"><span class="section-number-4">2.1.21</span> BEGIN_SIGNING</h4>
+<div class="outline-text-4" id="text-2-1-21">
+
+<p> Mark the start of the actual signing process. This may be used as
+ an indication that all requested secret keys are ready for use.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-22" class="outline-4">
+<h4 id="sec-2-1-22"><span class="section-number-4">2.1.22</span> ALREADY_SIGNED &lt;long-keyid&gt;</h4>
+<div class="outline-text-4" id="text-2-1-22">
+
+<p> Warning: This is experimental and might be removed at any time.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-23" class="outline-4">
+<h4 id="sec-2-1-23"><span class="section-number-4">2.1.23</span> SIG_CREATED &lt;type&gt; &lt;pk_algo&gt; &lt;hash_algo&gt; &lt;class&gt; &lt;timestamp&gt; &lt;keyfpr&gt;</h4>
+<div class="outline-text-4" id="text-2-1-23">
+
+<p> A signature has been created using these parameters.
+ Values for type &lt;type&gt; are:
+</p><dl>
+<dt>D</dt><dd>detached
+</dd>
+<dt>C</dt><dd>cleartext
+</dd>
+<dt>S</dt><dd>standard
+</dd>
+</dl>
+
+<p> (only the first character should be checked)
+</p>
+<p>
+ &lt;class&gt; are 2 hex digits with the OpenPGP signature class.
+</p>
+<p>
+ Note, that TIMESTAMP may either be a number of seconds since Epoch
+ or an ISO 8601 string which can be detected by the presence of the
+ letter 'T'.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-24" class="outline-4">
+<h4 id="sec-2-1-24"><span class="section-number-4">2.1.24</span> NOTATION_</h4>
+<div class="outline-text-4" id="text-2-1-24">
+
+<p> There are actually two related status codes to convey notation
+ data:
+</p>
+<ul>
+<li>NOTATION_NAME &lt;name&gt;
+</li>
+<li>NOTATION_DATA &lt;string&gt;
+</li>
+</ul>
+
+
+<p>
+ &lt;name&gt; and &lt;string&gt; are %XX escaped; the data may be split among
+ several NOTATION_DATA lines.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-25" class="outline-4">
+<h4 id="sec-2-1-25"><span class="section-number-4">2.1.25</span> POLICY_URL &lt;string&gt;</h4>
+<div class="outline-text-4" id="text-2-1-25">
+
+<p> Note that URL in &lt;string&gt; is %XX escaped.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-26" class="outline-4">
+<h4 id="sec-2-1-26"><span class="section-number-4">2.1.26</span> PLAINTEXT &lt;format&gt; &lt;timestamp&gt; &lt;filename&gt;</h4>
+<div class="outline-text-4" id="text-2-1-26">
+
+<p> This indicates the format of the plaintext that is about to be
+ written. The format is a 1 byte hex code that shows the format of
+ the plaintext: 62 ('b') is binary data, 74 ('t') is text data with
+ no character set specified, and 75 ('u') is text data encoded in
+ the UTF-8 character set. The timestamp is in seconds since the
+ epoch. If a filename is available it gets printed as the third
+ argument, percent-escaped as usual.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-27" class="outline-4">
+<h4 id="sec-2-1-27"><span class="section-number-4">2.1.27</span> PLAINTEXT_LENGTH &lt;length&gt;</h4>
+<div class="outline-text-4" id="text-2-1-27">
+
+<p> This indicates the length of the plaintext that is about to be
+ written. Note that if the plaintext packet has partial length
+ encoding it is not possible to know the length ahead of time. In
+ that case, this status tag does not appear.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-1-28" class="outline-4">
+<h4 id="sec-2-1-28"><span class="section-number-4">2.1.28</span> ATTRIBUTE &lt;arguments&gt;</h4>
+<div class="outline-text-4" id="text-2-1-28">
+
+<p> The list or argemnts are:
+</p><ul>
+<li>&lt;fpr&gt;
+</li>
+<li>&lt;octets&gt;
+</li>
+<li>&lt;type&gt;
+</li>
+<li>&lt;index&gt;
+</li>
+<li>&lt;count&gt;
+</li>
+<li>&lt;timestamp&gt;
+</li>
+<li>&lt;expiredate&gt;
+</li>
+<li>&lt;flags&gt;
+</li>
+</ul>
+
+
+<p>
+ This is one long line issued for each attribute subpacket when an
+ attribute packet is seen during key listing. &lt;fpr&gt; is the
+ fingerprint of the key. &lt;octets&gt; is the length of the attribute
+ subpacket. &lt;type&gt; is the attribute type (e.g. 1 for an image).
+ &lt;index&gt; and &lt;count&gt; indicate that this is the N-th indexed
+ subpacket of count total subpackets in this attribute packet.
+ &lt;timestamp&gt; and &lt;expiredate&gt; are from the self-signature on the
+ attribute packet. If the attribute packet does not have a valid
+ self-signature, then the timestamp is 0. &lt;flags&gt; are a bitwise OR
+ of:
+</p><dl>
+<dt>0x01</dt><dd>this attribute packet is a primary uid
+</dd>
+<dt>0x02</dt><dd>this attribute packet is revoked
+</dd>
+<dt>0x04</dt><dd>this attribute packet is expired
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-1-29" class="outline-4">
+<h4 id="sec-2-1-29"><span class="section-number-4">2.1.29</span> SIG_SUBPACKET &lt;type&gt; &lt;flags&gt; &lt;len&gt; &lt;data&gt;</h4>
+<div class="outline-text-4" id="text-2-1-29">
+
+<p> This indicates that a signature subpacket was seen. The format is
+ the same as the "spk" record above.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2-2" class="outline-3">
+<h3 id="sec-2-2"><span class="section-number-3">2.2</span> Key related</h3>
+<div class="outline-text-3" id="text-2-2">
+
+
+</div>
+
+<div id="outline-container-2-2-1" class="outline-4">
+<h4 id="sec-2-2-1"><span class="section-number-4">2.2.1</span> INV_RECP, INV_SGNR</h4>
+<div class="outline-text-4" id="text-2-2-1">
+
+<p> The two similar status codes:
+</p>
+<ul>
+<li>INV_RECP &lt;reason&gt; &lt;requested_recipient&gt;
+</li>
+<li>INV_SGNR &lt;reason&gt; &lt;requested_sender&gt;
+</li>
+</ul>
+
+
+<p>
+ are issued for each unusable recipient/sender. The reasons codes
+ currently in use are:
+</p>
+<dl>
+<dt>0</dt><dd>No specific reason given
+</dd>
+<dt>1</dt><dd>Not Found
+</dd>
+<dt>2</dt><dd>Ambigious specification
+</dd>
+<dt>3</dt><dd>Wrong key usage
+</dd>
+<dt>4</dt><dd>Key revoked
+</dd>
+<dt>5</dt><dd>Key expired
+</dd>
+<dt>6</dt><dd>No CRL known
+</dd>
+<dt>7</dt><dd>CRL too old
+</dd>
+<dt>8</dt><dd>Policy mismatch
+</dd>
+<dt>9</dt><dd>Not a secret key
+</dd>
+<dt>10</dt><dd>Key not trusted
+</dd>
+<dt>11</dt><dd>Missing certificate
+</dd>
+<dt>12</dt><dd>Missing issuer certificate
+</dd>
+</dl>
+
+
+<p>
+ Note that for historical reasons the INV_RECP status is also used
+ for gpgsm's SIGNER command where it relates to signer's of course.
+ Newer GnuPG versions are using INV_SGNR; applications should
+ ignore the INV_RECP during the sender's command processing once
+ they have seen an INV_SGNR. Different codes are used so that they
+ can be distinguish while doing an encrypt+sign operation.
+</p></div>
+
+</div>
+
+<div id="outline-container-2-2-2" class="outline-4">
+<h4 id="sec-2-2-2"><span class="section-number-4">2.2.2</span> NO_RECP &lt;reserved&gt;</h4>
+<div class="outline-text-4" id="text-2-2-2">
+
+<p> Issued if no recipients are usable.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-3" class="outline-4">
+<h4 id="sec-2-2-3"><span class="section-number-4">2.2.3</span> NO_SGNR &lt;reserved&gt;</h4>
+<div class="outline-text-4" id="text-2-2-3">
+
+<p> Issued if no senders are usable.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-4" class="outline-4">
+<h4 id="sec-2-2-4"><span class="section-number-4">2.2.4</span> KEYEXPIRED &lt;expire-timestamp&gt;</h4>
+<div class="outline-text-4" id="text-2-2-4">
+
+<p> The key has expired. expire-timestamp is the expiration time in
+ seconds since Epoch. This status line is not very useful because
+ it will also be emitted for expired subkeys even if this subkey is
+ not used. To check whether a key used to sign a message has
+ expired, the EXPKEYSIG status line is to be used.
+</p>
+<p>
+ Note, that the TIMESTAMP may either be a number of seconds since
+ Epoch or an ISO 8601 string which can be detected by the presence
+ of the letter 'T'.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-5" class="outline-4">
+<h4 id="sec-2-2-5"><span class="section-number-4">2.2.5</span> KEYREVOKED</h4>
+<div class="outline-text-4" id="text-2-2-5">
+
+<p> The used key has been revoked by its owner. No arguments yet.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-6" class="outline-4">
+<h4 id="sec-2-2-6"><span class="section-number-4">2.2.6</span> NO_PUBKEY &lt;long keyid&gt;</h4>
+<div class="outline-text-4" id="text-2-2-6">
+
+<p> The public key is not available
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-7" class="outline-4">
+<h4 id="sec-2-2-7"><span class="section-number-4">2.2.7</span> NO_SECKEY &lt;long keyid&gt;</h4>
+<div class="outline-text-4" id="text-2-2-7">
+
+<p> The secret key is not available
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-8" class="outline-4">
+<h4 id="sec-2-2-8"><span class="section-number-4">2.2.8</span> KEY_CREATED &lt;type&gt; &lt;fingerprint&gt; [&lt;handle&gt;]</h4>
+<div class="outline-text-4" id="text-2-2-8">
+
+<p> A key has been created. Values for &lt;type&gt; are:
+</p><dl>
+<dt>B</dt><dd>primary and subkey
+</dd>
+<dt>P</dt><dd>primary
+</dd>
+<dt>S</dt><dd>subkey
+</dd>
+</dl>
+
+<p> The fingerprint is one of the primary key for type B and P and the
+ one of the subkey for S. Handle is an arbitrary non-whitespace
+ string used to match key parameters from batch key creation run.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-9" class="outline-4">
+<h4 id="sec-2-2-9"><span class="section-number-4">2.2.9</span> KEY_NOT_CREATED [&lt;handle&gt;]</h4>
+<div class="outline-text-4" id="text-2-2-9">
+
+<p> The key from batch run has not been created due to errors.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-10" class="outline-4">
+<h4 id="sec-2-2-10"><span class="section-number-4">2.2.10</span> TRUST_</h4>
+<div class="outline-text-4" id="text-2-2-10">
+
+<p> These are several similar status codes:
+</p>
+<ul>
+<li>TRUST_UNDEFINED &lt;error_token&gt;
+</li>
+<li>TRUST_NEVER &lt;error_token&gt;
+</li>
+<li>TRUST_MARGINAL [0 [&lt;validation_model&gt;]]
+</li>
+<li>TRUST_FULLY [0 [&lt;validation_model&gt;]]
+</li>
+<li>TRUST_ULTIMATE [0 [&lt;validation_model&gt;]]
+</li>
+</ul>
+
+
+<p>
+ For good signatures one of these status lines are emitted to
+ indicate the validity of the key used to create the signature.
+ The error token values are currently only emitted by gpgsm.
+</p>
+<p>
+ VALIDATION_MODEL describes the algorithm used to check the
+ validity of the key. The defaults are the standard Web of Trust
+ model for gpg and the the standard X.509 model for gpgsm. The
+ defined values are
+</p>
+<dl>
+<dt>pgp </dt><dd>The standard PGP WoT.
+</dd>
+<dt>shell</dt><dd>The standard X.509 model.
+</dd>
+<dt>chain</dt><dd>The chain model.
+</dd>
+<dt>steed</dt><dd>The STEED model.
+</dd>
+</dl>
+
+
+<p>
+ Note that the term <code>TRUST_</code> in the status names is used for
+ historic reasons; we now speak of validity.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-2-11" class="outline-4">
+<h4 id="sec-2-2-11"><span class="section-number-4">2.2.11</span> PKA_TRUST_</h4>
+<div class="outline-text-4" id="text-2-2-11">
+
+<p> This is is one:
+</p>
+<ul>
+<li>PKA_TRUST_GOOD &lt;mailbox&gt;
+</li>
+<li>PKA_TRUST_BAD &lt;mailbox&gt;
+</li>
+</ul>
+
+
+<p>
+ Depending on the outcome of the PKA check one of the above status
+ codes is emitted in addition to a <code>TRUST_*</code> status.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2-3" class="outline-3">
+<h3 id="sec-2-3"><span class="section-number-3">2.3</span> Remote control</h3>
+<div class="outline-text-3" id="text-2-3">
+
+
+</div>
+
+<div id="outline-container-2-3-1" class="outline-4">
+<h4 id="sec-2-3-1"><span class="section-number-4">2.3.1</span> GET_BOOL, GET_LINE, GET_HIDDEN, GOT_IT</h4>
+<div class="outline-text-4" id="text-2-3-1">
+
+
+<p>
+ These status line are used with &ndash;command-fd for interactive
+ control of the process.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-3-2" class="outline-4">
+<h4 id="sec-2-3-2"><span class="section-number-4">2.3.2</span> USERID_HINT &lt;long main keyid&gt; &lt;string&gt;</h4>
+<div class="outline-text-4" id="text-2-3-2">
+
+<p> Give a hint about the user ID for a certain keyID.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-3-3" class="outline-4">
+<h4 id="sec-2-3-3"><span class="section-number-4">2.3.3</span> NEED_PASSPHRASE &lt;long keyid&gt; &lt;long main keyid&gt; &lt;keytype&gt; &lt;keylength&gt;</h4>
+<div class="outline-text-4" id="text-2-3-3">
+
+<p> Issued whenever a passphrase is needed. KEYTYPE is the numerical
+ value of the public key algorithm or 0 if this is not applicable,
+ KEYLENGTH is the length of the key or 0 if it is not known (this
+ is currently always the case).
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-3-4" class="outline-4">
+<h4 id="sec-2-3-4"><span class="section-number-4">2.3.4</span> NEED_PASSPHRASE_SYM &lt;cipher_algo&gt; &lt;s2k_mode&gt; &lt;s2k_hash&gt;</h4>
+<div class="outline-text-4" id="text-2-3-4">
+
+<p> Issued whenever a passphrase for symmetric encryption is needed.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-3-5" class="outline-4">
+<h4 id="sec-2-3-5"><span class="section-number-4">2.3.5</span> NEED_PASSPHRASE_PIN &lt;card_type&gt; &lt;chvno&gt; [&lt;serialno&gt;]</h4>
+<div class="outline-text-4" id="text-2-3-5">
+
+<p> Issued whenever a PIN is requested to unlock a card.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-3-6" class="outline-4">
+<h4 id="sec-2-3-6"><span class="section-number-4">2.3.6</span> MISSING_PASSPHRASE</h4>
+<div class="outline-text-4" id="text-2-3-6">
+
+<p> No passphrase was supplied. An application which encounters this
+ message may want to stop parsing immediately because the next
+ message will probably be a BAD_PASSPHRASE. However, if the
+ application is a wrapper around the key edit menu functionality it
+ might not make sense to stop parsing but simply ignoring the
+ following BAD_PASSPHRASE.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-3-7" class="outline-4">
+<h4 id="sec-2-3-7"><span class="section-number-4">2.3.7</span> BAD_PASSPHRASE &lt;long keyid&gt;</h4>
+<div class="outline-text-4" id="text-2-3-7">
+
+<p> The supplied passphrase was wrong or not given. In the latter
+ case you may have seen a MISSING_PASSPHRASE.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-3-8" class="outline-4">
+<h4 id="sec-2-3-8"><span class="section-number-4">2.3.8</span> GOOD_PASSPHRASE</h4>
+<div class="outline-text-4" id="text-2-3-8">
+
+<p> The supplied passphrase was good and the secret key material
+ is therefore usable.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2-4" class="outline-3">
+<h3 id="sec-2-4"><span class="section-number-3">2.4</span> Import/Export</h3>
+<div class="outline-text-3" id="text-2-4">
+
+
+</div>
+
+<div id="outline-container-2-4-1" class="outline-4">
+<h4 id="sec-2-4-1"><span class="section-number-4">2.4.1</span> IMPORT_CHECK &lt;long keyid&gt; &lt;fingerprint&gt; &lt;user ID&gt;</h4>
+<div class="outline-text-4" id="text-2-4-1">
+
+<p> This status is emitted in interactive mode right before
+ the "import.okay" prompt.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-4-2" class="outline-4">
+<h4 id="sec-2-4-2"><span class="section-number-4">2.4.2</span> IMPORTED &lt;long keyid&gt; &lt;username&gt;</h4>
+<div class="outline-text-4" id="text-2-4-2">
+
+<p> The keyid and name of the signature just imported
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-4-3" class="outline-4">
+<h4 id="sec-2-4-3"><span class="section-number-4">2.4.3</span> IMPORT_OK &lt;reason&gt; [&lt;fingerprint&gt;]</h4>
+<div class="outline-text-4" id="text-2-4-3">
+
+<p> The key with the primary key's FINGERPRINT has been imported.
+ REASON flags are:
+</p>
+<dl>
+<dt>0</dt><dd>Not actually changed
+</dd>
+<dt>1</dt><dd>Entirely new key.
+</dd>
+<dt>2</dt><dd>New user IDs
+</dd>
+<dt>4</dt><dd>New signatures
+</dd>
+<dt>8</dt><dd>New subkeys
+</dd>
+<dt>16</dt><dd>Contains private key.
+</dd>
+</dl>
+
+
+<p>
+ The flags may be ORed.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-4-4" class="outline-4">
+<h4 id="sec-2-4-4"><span class="section-number-4">2.4.4</span> IMPORT_PROBLEM &lt;reason&gt; [&lt;fingerprint&gt;]</h4>
+<div class="outline-text-4" id="text-2-4-4">
+
+<p> Issued for each import failure. Reason codes are:
+</p>
+<dl>
+<dt>0</dt><dd>No specific reason given.
+</dd>
+<dt>1</dt><dd>Invalid Certificate.
+</dd>
+<dt>2</dt><dd>Issuer Certificate missing.
+</dd>
+<dt>3</dt><dd>Certificate Chain too long.
+</dd>
+<dt>4</dt><dd>Error storing certificate.
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-4-5" class="outline-4">
+<h4 id="sec-2-4-5"><span class="section-number-4">2.4.5</span> IMPORT_RES &lt;args&gt;</h4>
+<div class="outline-text-4" id="text-2-4-5">
+
+<p> Final statistics on import process (this is one long line). The
+ args are a list of unsigned numbers separated by white space:
+</p>
+<ul>
+<li>&lt;count&gt;
+</li>
+<li>&lt;no_user_id&gt;
+</li>
+<li>&lt;imported&gt;
+</li>
+<li>&lt;imported_rsa&gt;
+</li>
+<li>&lt;unchanged&gt;
+</li>
+<li>&lt;n_uids&gt;
+</li>
+<li>&lt;n_subk&gt;
+</li>
+<li>&lt;n_sigs&gt;
+</li>
+<li>&lt;n_revoc&gt;
+</li>
+<li>&lt;sec_read&gt;
+</li>
+<li>&lt;sec_imported&gt;
+</li>
+<li>&lt;sec_dups&gt;
+</li>
+<li>&lt;skipped_new_keys&gt;
+</li>
+<li>&lt;not_imported&gt;
+</li>
+</ul>
+
+
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2-5" class="outline-3">
+<h3 id="sec-2-5"><span class="section-number-3">2.5</span> Smartcard related</h3>
+<div class="outline-text-3" id="text-2-5">
+
+
+</div>
+
+<div id="outline-container-2-5-1" class="outline-4">
+<h4 id="sec-2-5-1"><span class="section-number-4">2.5.1</span> CARDCTRL &lt;what&gt; [&lt;serialno&gt;]</h4>
+<div class="outline-text-4" id="text-2-5-1">
+
+<p> This is used to control smartcard operations. Defined values for
+ WHAT are:
+</p>
+<dl>
+<dt>1</dt><dd>Request insertion of a card. Serialnumber may be given
+ to request a specific card. Used by gpg 1.4 w/o
+ scdaemon
+</dd>
+<dt>2</dt><dd>Request removal of a card. Used by gpg 1.4 w/o scdaemon.
+</dd>
+<dt>3</dt><dd>Card with serialnumber detected
+</dd>
+<dt>4</dt><dd>No card available
+</dd>
+<dt>5</dt><dd>No card reader available
+</dd>
+<dt>6</dt><dd>No card support available
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-5-2" class="outline-4">
+<h4 id="sec-2-5-2"><span class="section-number-4">2.5.2</span> SC_OP_FAILURE [&lt;code&gt;]</h4>
+<div class="outline-text-4" id="text-2-5-2">
+
+<p> An operation on a smartcard definitely failed. Currently there is
+ no indication of the actual error code, but application should be
+ prepared to later accept more arguments. Defined values for
+ &lt;code&gt; are:
+</p>
+<dl>
+<dt>0</dt><dd>unspecified error (identically to a missing CODE)
+</dd>
+<dt>1</dt><dd>canceled
+</dd>
+<dt>2</dt><dd>bad PIN
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-5-3" class="outline-4">
+<h4 id="sec-2-5-3"><span class="section-number-4">2.5.3</span> SC_OP_SUCCESS</h4>
+<div class="outline-text-4" id="text-2-5-3">
+
+<p> A smart card operaion succeeded. This status is only printed for
+ certain operation and is mostly useful to check whether a PIN
+ change really worked.
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2-6" class="outline-3">
+<h3 id="sec-2-6"><span class="section-number-3">2.6</span> Miscellaneous status codes</h3>
+<div class="outline-text-3" id="text-2-6">
+
+
+</div>
+
+<div id="outline-container-2-6-1" class="outline-4">
+<h4 id="sec-2-6-1"><span class="section-number-4">2.6.1</span> NODATA &lt;what&gt;</h4>
+<div class="outline-text-4" id="text-2-6-1">
+
+<p> No data has been found. Codes for WHAT are:
+</p>
+<dl>
+<dt>1</dt><dd>No armored data.
+</dd>
+<dt>2</dt><dd>Expected a packet but did not found one.
+</dd>
+<dt>3</dt><dd>Invalid packet found, this may indicate a non OpenPGP
+ message.
+</dd>
+<dt>4</dt><dd>Signature expected but not found
+</dd>
+</dl>
+
+
+<p>
+ You may see more than one of these status lines.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-6-2" class="outline-4">
+<h4 id="sec-2-6-2"><span class="section-number-4">2.6.2</span> UNEXPECTED &lt;what&gt;</h4>
+<div class="outline-text-4" id="text-2-6-2">
+
+<p> Unexpected data has been encountered. Codes for WHAT are:
+</p><dl>
+<dt>0</dt><dd>Not further specified
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-6-3" class="outline-4">
+<h4 id="sec-2-6-3"><span class="section-number-4">2.6.3</span> TRUNCATED &lt;maxno&gt;</h4>
+<div class="outline-text-4" id="text-2-6-3">
+
+<p> The output was truncated to MAXNO items. This status code is
+ issued for certain external requests.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-6-4" class="outline-4">
+<h4 id="sec-2-6-4"><span class="section-number-4">2.6.4</span> ERROR &lt;error location&gt; &lt;error code&gt; [&lt;more&gt;]</h4>
+<div class="outline-text-4" id="text-2-6-4">
+
+<p> This is a generic error status message, it might be followed by
+ error location specific data. &lt;error code&gt; and &lt;error_location&gt;
+ should not contain spaces. The error code is a either a string
+ commencing with a letter or such a string prefixed with a
+ numerical error code and an underscore; e.g.: "151011327_EOF".
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-6-5" class="outline-4">
+<h4 id="sec-2-6-5"><span class="section-number-4">2.6.5</span> SUCCESS [&lt;location&gt;]</h4>
+<div class="outline-text-4" id="text-2-6-5">
+
+<p> Postive confirimation that an operation succeeded. &lt;location&gt; is
+ optional but if given should not contain spaces. Used only with a
+ few commands.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-6-6" class="outline-4">
+<h4 id="sec-2-6-6"><span class="section-number-4">2.6.6</span> BADARMOR</h4>
+<div class="outline-text-4" id="text-2-6-6">
+
+<p> The ASCII armor is corrupted. No arguments yet.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-6-7" class="outline-4">
+<h4 id="sec-2-6-7"><span class="section-number-4">2.6.7</span> DELETE_PROBLEM &lt;reason_code&gt;</h4>
+<div class="outline-text-4" id="text-2-6-7">
+
+<p> Deleting a key failed. Reason codes are:
+</p><dl>
+<dt>1</dt><dd>No such key
+</dd>
+<dt>2</dt><dd>Must delete secret key first
+</dd>
+<dt>3</dt><dd>Ambigious specification
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-6-8" class="outline-4">
+<h4 id="sec-2-6-8"><span class="section-number-4">2.6.8</span> PROGRESS &lt;what&gt; &lt;char&gt; &lt;cur&gt; &lt;total&gt;</h4>
+<div class="outline-text-4" id="text-2-6-8">
+
+<p> Used by the primegen and Public key functions to indicate
+ progress. &lt;char&gt; is the character displayed with no &ndash;status-fd
+ enabled, with the linefeed replaced by an 'X'. &lt;cur&gt; is the
+ current amount done and &lt;total&gt; is amount to be done; a &lt;total&gt; of
+ 0 indicates that the total amount is not known. The condition
+</p><pre class="example">
+ TOTAL &amp;&amp; CUR == TOTAL
+</pre>
+
+<p> may be used to detect the end of an operation.
+</p>
+<p>
+ Well known values for WHAT are:
+</p>
+<dl>
+<dt>pk_dsa </dt><dd>DSA key generation
+</dd>
+<dt>pk_elg </dt><dd>Elgamal key generation
+</dd>
+<dt>primegen</dt><dd>Prime generation
+</dd>
+<dt>need_entropy</dt><dd>Waiting for new entropy in the RNG
+</dd>
+<dt>tick</dt><dd>Generic tick without any special meaning - useful
+ for letting clients know that the server is still
+ working.
+</dd>
+<dt>starting_agent</dt><dd>A gpg-agent was started because it is not
+ running as a daemon.
+</dd>
+<dt>learncard</dt><dd>Send by the agent and gpgsm while learing
+ the data of a smartcard.
+</dd>
+<dt>card_busy</dt><dd>A smartcard is still working
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-2-6-9" class="outline-4">
+<h4 id="sec-2-6-9"><span class="section-number-4">2.6.9</span> BACKUP_KEY_CREATED &lt;fingerprint&gt; &lt;fname&gt;</h4>
+<div class="outline-text-4" id="text-2-6-9">
+
+<p> A backup of a key identified by &lt;fingerprint&gt; has been writte to
+ the file &lt;fname&gt;; &lt;fname&gt; is percent-escaped.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-6-10" class="outline-4">
+<h4 id="sec-2-6-10"><span class="section-number-4">2.6.10</span> MOUNTPOINT &lt;name&gt;</h4>
+<div class="outline-text-4" id="text-2-6-10">
+
+<p> &lt;name&gt; is a percent-plus escaped filename describing the
+ mountpoint for the current operation (e.g. used by "g13 &ndash;mount").
+ This may either be the specified mountpoint or one randomly
+ choosen by g13.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-2-6-11" class="outline-4">
+<h4 id="sec-2-6-11"><span class="section-number-4">2.6.11</span> PINENTRY_LAUNCHED &lt;pid&gt;</h4>
+<div class="outline-text-4" id="text-2-6-11">
+
+<p> This status line is emitted by gpg to notify a client that a
+ Pinentry has been launched. &lt;pid&gt; is the PID of the Pinentry. It
+ may be used to display a hint to the user but can't be used to
+ synchronize with Pinentry. Note that there is also an Assuan
+ inquiry line with the same name used internally or, if enabled,
+ send to the client instead of this status line. Such an inquiry
+ may be used to sync with Pinentry
+</p>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-2-7" class="outline-3">
+<h3 id="sec-2-7"><span class="section-number-3">2.7</span> Obsolete status codes</h3>
+<div class="outline-text-3" id="text-2-7">
+
+
+</div>
+
+<div id="outline-container-2-7-1" class="outline-4">
+<h4 id="sec-2-7-1"><span class="section-number-4">2.7.1</span> SIGEXPIRED</h4>
+<div class="outline-text-4" id="text-2-7-1">
+
+<p> Removed on 2011-02-04. This is deprecated in favor of KEYEXPIRED.
+</p></div>
+
+</div>
+
+<div id="outline-container-2-7-2" class="outline-4">
+<h4 id="sec-2-7-2"><span class="section-number-4">2.7.2</span> RSA_OR_IDEA</h4>
+<div class="outline-text-4" id="text-2-7-2">
+
+<p> Obsolete. This status message used to be emitted for requests to
+ use the IDEA or RSA algorithms. It has been dropped from GnuPG
+ 2.1 after the respective patents expired.
+</p></div>
+
+</div>
+
+<div id="outline-container-2-7-3" class="outline-4">
+<h4 id="sec-2-7-3"><span class="section-number-4">2.7.3</span> SHM_INFO, SHM_GET, SHM_GET_BOOL, SHM_GET_HIDDEN</h4>
+<div class="outline-text-4" id="text-2-7-3">
+
+<p> These were used for the ancient shared memory based co-processing.
+</p></div>
+
+</div>
+
+<div id="outline-container-2-7-4" class="outline-4">
+<h4 id="sec-2-7-4"><span class="section-number-4">2.7.4</span> BEGIN_STREAM, END_STREAM</h4>
+<div class="outline-text-4" id="text-2-7-4">
+
+<p> Used to issued by the experimental pipemode.
+</p>
+
+</div>
+</div>
+</div>
+
+</div>
+
+<div id="outline-container-3" class="outline-2">
+<h2 id="sec-3"><span class="section-number-2">3</span> Format of the &ndash;attribute-fd output</h2>
+<div class="outline-text-2" id="text-3">
+
+
+<p>
+ When &ndash;attribute-fd is set, during key listings (&ndash;list-keys,
+ &ndash;list-secret-keys) GnuPG dumps each attribute packet to the file
+ descriptor specified. &ndash;attribute-fd is intended for use with
+ &ndash;status-fd as part of the required information is carried on the
+ ATTRIBUTE status tag (see above).
+</p>
+<p>
+ The contents of the attribute data is specified by RFC 4880. For
+ convenience, here is the Photo ID format, as it is currently the
+ only attribute defined:
+</p>
+<dl>
+<dt>Byte 0-1</dt><dd>The length of the image header. Due to a historical
+ accident (i.e. oops!) back in the NAI PGP days, this
+ is a little-endian number. Currently 16 (0x10 0x00).
+
+</dd>
+<dt>Byte 2</dt><dd>The image header version. Currently 0x01.
+
+</dd>
+<dt>Byte 3</dt><dd>Encoding format. 0x01 == JPEG.
+
+</dd>
+<dt>Byte 4-15</dt><dd>Reserved, and currently unused.
+</dd>
+</dl>
+
+
+<p>
+ All other data after this header is raw image (JPEG) data.
+</p>
+
+</div>
+
+</div>
+
+<div id="outline-container-4" class="outline-2">
+<h2 id="sec-4"><span class="section-number-2">4</span> Unattended key generation</h2>
+<div class="outline-text-2" id="text-4">
+
+
+<p>
+ Please see the GnuPG manual for a description.
+</p>
+
+</div>
+
+</div>
+
+<div id="outline-container-5" class="outline-2">
+<h2 id="sec-5"><span class="section-number-2">5</span> Layout of the TrustDB</h2>
+<div class="outline-text-2" id="text-5">
+
+
+<p>
+ The TrustDB is built from fixed length records, where the first byte
+ describes the record type. All numeric values are stored in network
+ byte order. The length of each record is 40 bytes. The first record
+ of the DB is always of type 1 and this is the only record of this
+ type.
+</p>
+<p>
+ FIXME: The layout changed, document it here.
+</p>
+
+
+<pre class="example">Record type 0:
+--------------
+ Unused record, can be reused for any purpose.
+
+Record type 1:
+--------------
+ Version information for this TrustDB. This is always the first
+ record of the DB and the only one with type 1.
+ 1 byte value 1
+ 3 bytes 'gpg' magic value
+ 1 byte Version of the TrustDB (2)
+ 1 byte marginals needed
+ 1 byte completes needed
+ 1 byte max_cert_depth
+ The three items are used to check whether the cached
+ validity value from the dir record can be used.
+ 1 u32 locked flags [not used]
+ 1 u32 timestamp of trustdb creation
+ 1 u32 timestamp of last modification which may affect the validity
+ of keys in the trustdb. This value is checked against the
+ validity timestamp in the dir records.
+ 1 u32 timestamp of last validation [currently not used]
+ (Used to keep track of the time, when this TrustDB was checked
+ against the pubring)
+ 1 u32 record number of keyhashtable [currently not used]
+ 1 u32 first free record
+ 1 u32 record number of shadow directory hash table [currently not used]
+ It does not make sense to combine this table with the key table
+ because the keyid is not in every case a part of the fingerprint.
+ 1 u32 record number of the trusthashtbale
+
+
+Record type 2: (directory record)
+--------------
+ Informations about a public key certificate.
+ These are static values which are never changed without user interaction.
+
+ 1 byte value 2
+ 1 byte reserved
+ 1 u32 LID . (This is simply the record number of this record.)
+ 1 u32 List of key-records (the first one is the primary key)
+ 1 u32 List of uid-records
+ 1 u32 cache record
+ 1 byte ownertrust
+ 1 byte dirflag
+ 1 byte maximum validity of all the user ids
+ 1 u32 time of last validity check.
+ 1 u32 Must check when this time has been reached.
+ (0 = no check required)
+
+
+Record type 3: (key record)
+--------------
+ Informations about a primary public key.
+ (This is mainly used to lookup a trust record)
+
+ 1 byte value 3
+ 1 byte reserved
+ 1 u32 LID
+ 1 u32 next - next key record
+ 7 bytes reserved
+ 1 byte keyflags
+ 1 byte pubkey algorithm
+ 1 byte length of the fingerprint (in bytes)
+ 20 bytes fingerprint of the public key
+ (This is the value we use to identify a key)
+
+Record type 4: (uid record)
+--------------
+ Informations about a userid
+ We do not store the userid but the hash value of the userid because that
+ is sufficient.
+
+ 1 byte value 4
+ 1 byte reserved
+ 1 u32 LID points to the directory record.
+ 1 u32 next next userid
+ 1 u32 pointer to preference record
+ 1 u32 siglist list of valid signatures
+ 1 byte uidflags
+ 1 byte validity of the key calculated over this user id
+ 20 bytes ripemd160 hash of the username.
+
+
+Record type 5: (pref record)
+--------------
+ This record type is not anymore used.
+
+ 1 byte value 5
+ 1 byte reserved
+ 1 u32 LID; points to the directory record (and not to the uid record!).
+ (or 0 for standard preference record)
+ 1 u32 next
+ 30 byte preference data
+
+Record type 6 (sigrec)
+-------------
+ Used to keep track of key signatures. Self-signatures are not
+ stored. If a public key is not in the DB, the signature points to
+ a shadow dir record, which in turn has a list of records which
+ might be interested in this key (and the signature record here
+ is one).
+
+ 1 byte value 6
+ 1 byte reserved
+ 1 u32 LID points back to the dir record
+ 1 u32 next next sigrec of this uid or 0 to indicate the
+ last sigrec.
+ 6 times
+ 1 u32 Local_id of signatures dir or shadow dir record
+ 1 byte Flag: Bit 0 = checked: Bit 1 is valid (we have a real
+ directory record for this)
+ 1 = valid is set (but may be revoked)
+
+
+
+Record type 8: (shadow directory record)
+--------------
+ This record is used to reserve a LID for a public key. We
+ need this to create the sig records of other keys, even if we
+ do not yet have the public key of the signature.
+ This record (the record number to be more precise) will be reused
+ as the dir record when we import the real public key.
+
+ 1 byte value 8
+ 1 byte reserved
+ 1 u32 LID (This is simply the record number of this record.)
+ 2 u32 keyid
+ 1 byte pubkey algorithm
+ 3 byte reserved
+ 1 u32 hintlist A list of records which have references to
+ this key. This is used for fast access to
+ signature records which are not yet checked.
+ Note, that this is only a hint and the actual records
+ may not anymore hold signature records for that key
+ but that the code cares about this.
+ 18 byte reserved
+
+
+
+Record Type 10 (hash table)
+--------------
+ Due to the fact that we use fingerprints to lookup keys, we can
+ implement quick access by some simple hash methods, and avoid
+ the overhead of gdbm. A property of fingerprints is that they can be
+ used directly as hash values. (They can be considered as strong
+ random numbers.)
+ What we use is a dynamic multilevel architecture, which combines
+ hashtables, record lists, and linked lists.
+
+ This record is a hashtable of 256 entries; a special property
+ is that all these records are stored consecutively to make one
+ big table. The hash value is simple the 1st, 2nd, ... byte of
+ the fingerprint (depending on the indirection level).
+
+ When used to hash shadow directory records, a different table is used
+ and indexed by the keyid.
+
+ 1 byte value 10
+ 1 byte reserved
+ n u32 recnum; n depends on the record length:
+ n = (reclen-2)/4 which yields 9 for the current record length
+ of 40 bytes.
+
+ the total number of such record which makes up the table is:
+ m = (256+n-1) / n
+ which is 29 for a record length of 40.
+
+ To look up a key we use the first byte of the fingerprint to get
+ the recnum from this hashtable and look up the addressed record:
+ - If this record is another hashtable, we use 2nd byte
+ to index this hash table and so on.
+ - if this record is a hashlist, we walk all entries
+ until we found one a matching one.
+ - if this record is a key record, we compare the
+ fingerprint and to decide whether it is the requested key;
+
+
+Record type 11 (hash list)
+--------------
+ see hash table for an explanation.
+ This is also used for other purposes.
+
+ 1 byte value 11
+ 1 byte reserved
+ 1 u32 next next hash list record
+ n times n = (reclen-5)/5
+ 1 u32 recnum
+
+ For the current record length of 40, n is 7
+
+
+
+Record type 254 (free record)
+---------------
+ All these records form a linked list of unused records.
+ 1 byte value 254
+ 1 byte reserved (0)
+ 1 u32 next_free
+</pre>
+
+
+
+</div>
+
+</div>
+
+<div id="outline-container-6" class="outline-2">
+<h2 id="sec-6"><span class="section-number-2">6</span> GNU extensions to the S2K algorithm</h2>
+<div class="outline-text-2" id="text-6">
+
+
+<p>
+ S2K mode 101 is used to identify these extensions.
+ After the hash algorithm the 3 bytes "GNU" are used to make
+ clear that these are extensions for GNU, the next bytes gives the
+ GNU protection mode - 1000. Defined modes are:
+</p><dl>
+<dt>1001</dt><dd>Do not store the secret part at all.
+</dd>
+<dt>1002</dt><dd>A stub to access smartcards (not used in 1.2.x)
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-7" class="outline-2">
+<h2 id="sec-7"><span class="section-number-2">7</span> Keyserver helper message format</h2>
+<div class="outline-text-2" id="text-7">
+
+
+<p>
+ The keyserver may be contacted by a Unix Domain socket or via TCP.
+</p>
+<p>
+ The format of a request is:
+</p>
+
+
+<pre class="example">command-tag
+"Content-length:" digits
+CRLF
+</pre>
+
+
+<p>
+ Where command-tag is
+</p>
+
+
+
+<pre class="example">NOOP
+GET &lt;user-name&gt;
+PUT
+DELETE &lt;user-name&gt;
+</pre>
+
+
+<p>
+The format of a response is:
+</p>
+
+
+
+<pre class="example">"GNUPG/1.0" status-code status-text
+"Content-length:" digits
+CRLF
+</pre>
+
+<p>
+followed by &lt;digits&gt; bytes of data
+</p>
+<p>
+Status codes are:
+</p>
+<dl>
+<dt>1xx</dt><dd>Informational - Request received, continuing process
+
+</dd>
+<dt>2xx</dt><dd>Success - The action was successfully received, understood,
+ and accepted
+
+</dd>
+<dt>4xx</dt><dd>Client Error - The request contains bad syntax or cannot be
+ fulfilled
+
+</dd>
+<dt>5xx</dt><dd>Server Error - The server failed to fulfill an apparently
+ valid request
+</dd>
+</dl>
+
+
+
+</div>
+
+</div>
+
+<div id="outline-container-8" class="outline-2">
+<h2 id="sec-8"><span class="section-number-2">8</span> Object identifiers</h2>
+<div class="outline-text-2" id="text-8">
+
+
+<p>
+ OIDs below the GnuPG arc:
+</p>
+
+
+
+<pre class="example">1.3.6.1.4.1.11591.2 GnuPG
+1.3.6.1.4.1.11591.2.1 notation
+1.3.6.1.4.1.11591.2.1.1 pkaAddress
+1.3.6.1.4.1.11591.2.2 X.509 extensions
+1.3.6.1.4.1.11591.2.2.1 standaloneCertificate
+1.3.6.1.4.1.11591.2.2.2 wellKnownPrivateKey
+1.3.6.1.4.1.11591.2.12242973 invalid encoded OID
+</pre>
+
+
+
+
+</div>
+
+</div>
+
+<div id="outline-container-9" class="outline-2">
+<h2 id="sec-9"><span class="section-number-2">9</span> Miscellaneous notes</h2>
+<div class="outline-text-2" id="text-9">
+
+
+
+</div>
+
+<div id="outline-container-9-1" class="outline-3">
+<h3 id="sec-9-1"><span class="section-number-3">9.1</span> v3 fingerprints</h3>
+<div class="outline-text-3" id="text-9-1">
+
+<p> For packet version 3 we calculate the keyids this way:
+</p><dl>
+<dt>RSA</dt><dd>Low 64 bits of n
+</dd>
+<dt>ELGAMAL</dt><dd>Build a v3 pubkey packet (with CTB 0x99) and
+ calculate a RMD160 hash value from it. This is used
+ as the fingerprint and the low 64 bits are the keyid.
+</dd>
+</dl>
+
+
+</div>
+
+</div>
+
+<div id="outline-container-9-2" class="outline-3">
+<h3 id="sec-9-2"><span class="section-number-3">9.2</span> Simplified revocation certificates</h3>
+<div class="outline-text-3" id="text-9-2">
+
+<p> Revocation certificates consist only of the signature packet;
+ "&ndash;import" knows how to handle this. The rationale behind it is to
+ keep them small.
+</p>
+</div>
+
+</div>
+
+<div id="outline-container-9-3" class="outline-3">
+<h3 id="sec-9-3"><span class="section-number-3">9.3</span> Documentation on HKP (the http keyserver protocol):</h3>
+<div class="outline-text-3" id="text-9-3">
+
+
+<p>
+ A minimalistic HTTP server on port 11371 recognizes a GET for
+ /pks/lookup. The standard http URL encoded query parameters are
+ this (always key=value):
+</p>
+<ul>
+<li>op=index (like pgp -kv), op=vindex (like pgp -kvv) and op=get (like
+ pgp -kxa)
+
+</li>
+<li>search=&lt;stringlist&gt;. This is a list of words that must occur in the key.
+ The words are delimited with space, points, @ and so on. The delimiters
+ are not searched for and the order of the words doesn't matter (but see
+ next option).
+
+</li>
+<li>exact=on. This switch tells the hkp server to only report exact matching
+ keys back. In this case the order and the "delimiters" are important.
+
+</li>
+<li>fingerprint=on. Also reports the fingerprints when used with 'index' or
+ 'vindex'
+</li>
+</ul>
+
+
+<p>
+ The keyserver also recognizes http-POSTs to /pks/add. Use this to upload
+ keys.
+</p>
+
+<p>
+ A better way to do this would be a request like:
+</p>
+<p>
+ /pks/lookup/&lt;gnupg_formatierte_user_id&gt;?op=&lt;operation&gt;
+</p>
+<p>
+ This can be implemented using Hurd's translator mechanism.
+ However, I think the whole key server stuff has to be re-thought;
+ I have some ideas and probably create a white paper.
+</p></div>
+</div>
+</div>
+</div>
+
+<div id="postamble">
+<p class="date">Date: 2013-07-03T09:52+0000</p>
+<p class="author">Author: isis</p>
+<p class="email"><a href="mailto:isis@wintermute.patternsinthevoid.net">isis@wintermute.patternsinthevoid.net</a></p>
+<p class="creator"><a href="http://orgmode.org">Org</a> version 7.9.2 with <a href="http://www.gnu.org/software/emacs/">Emacs</a> version 24</p>
+<a href="http://validator.w3.org/check?uri=referer">Validate XHTML 1.0</a>
+
+</div>
+</body>
+</html>
diff --git a/docs/_static/agogo.css b/docs/_static/agogo.css
new file mode 100644
index 0000000..2bdc26f
--- /dev/null
+++ b/docs/_static/agogo.css
@@ -0,0 +1,337 @@
+* {
+ margin: 0px;
+ padding: 0px;
+}
+
+body {
+ font-family: "Verdana", Arial, sans-serif;
+ line-height: 1.4em;
+ font-size: 14px;
+ color: black;
+ background-color: #eeeeec;
+}
+
+
+/* Page layout */
+
+div.header, div.content, div.footer {
+ width: 70em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+div.header-wrapper {
+ background: url(bgtop.png) top left repeat-x;
+ border-bottom: 3px solid #2e3436;
+}
+
+div.headertitle a {
+ font-family: "Georgia", "Times New Roman", serif;
+ font-size: 2em;
+ color: rgb(252, 175, 62);
+ font-weight: normal;
+}
+
+h1 {
+ color: #204a87;
+}
+
+/* Default body styles */
+a {
+ text-decoration: none;
+ color: #ce5c00;
+}
+
+.clearer {
+ clear: both;
+}
+
+.left {
+ float: left;
+}
+
+.right {
+ float: right;
+}
+
+h1, h2, h3, h4 {
+ font-family: "Georgia", "Times New Roman", serif;
+ font-weight: normal;
+ color: #3465a4;
+ margin-bottom: .8em;
+}
+
+h1 {
+ color: #204a87;
+}
+
+h2 {
+ padding-bottom: .5em;
+ border-bottom: 1px solid #3465a4;
+}
+
+a.headerlink {
+ visibility: hidden;
+ color: #dddddd;
+ padding-left: .3em;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink {
+ visibility: visible;
+}
+
+
+
+/* Header */
+
+div.header {
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+div.header h1 {
+ font-family: "Georgia", "Times New Roman", serif;
+ font-weight: normal;
+ font-size: 160%;
+ letter-spacing: .08em;
+}
+
+div.header h1 a {
+ color: white;
+}
+
+div.header div.rel {
+ margin-top: 1em;
+}
+
+div.header div.rel a {
+ color: #fcaf3e;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+}
+
+
+/* Content */
+div.content-wrapper {
+ background-color: white;
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+
+div.document {
+ width: 50em;
+ float: left;
+}
+
+div.body {
+ padding-right: 2em;
+ text-align: justify;
+}
+
+div.document ul {
+ margin-left: 1.2em;
+ list-style-type: square;
+}
+
+div.document dd {
+ margin-left: 1.2em;
+ margin-top: .4em;
+ margin-bottom: 1em;
+}
+
+div.document .section {
+ margin-top: 1.7em;
+}
+div.document .section:first-child {
+ margin-top: 0px;
+}
+
+div.document div.highlight {
+ padding: 3px;
+ background-color: #eeeeec;
+ border-top: 2px solid #dddddd;
+ border-bottom: 2px solid #dddddd;
+ margin-top: .8em;
+ margin-bottom: .8em;
+}
+
+div.document h2 {
+ margin-top: .7em;
+}
+
+div.document p {
+ margin-bottom: .5em;
+}
+
+div.document li.toctree-l1 {
+ margin-bottom: 1em;
+}
+
+div.document .descname {
+ font-weight: bold;
+}
+
+div.document .docutils.literal {
+ background-color: #eeeeec;
+ padding: 1px;
+}
+
+div.document .docutils.xref.literal {
+ background-color: transparent;
+ padding: 0px;
+}
+
+
+/* Sidebar */
+
+div.sidebar {
+ width: 20em;
+ float: right;
+ font-size: .9em;
+}
+
+div.sidebar h3 {
+ color: #2e3436;
+ text-transform: uppercase;
+ font-size: 130%;
+ letter-spacing: .1em;
+}
+
+div.sidebar ul {
+ list-style-type: none;
+}
+
+div.sidebar li.toctree-l1 a {
+ display: block;
+ padding: 1px;
+ border: 1px solid #dddddd;
+ background-color: #eeeeec;
+ margin-bottom: .4em;
+ padding-left: 3px;
+ color: #2e3436;
+}
+
+div.sidebar li.toctree-l2 a {
+ background-color: transparent;
+ border: none;
+ border-bottom: 1px solid #dddddd;
+}
+
+div.sidebar li.toctree-l2:last-child a {
+ border-bottom: none;
+}
+
+div.sidebar li.toctree-l1.current a {
+ border-right: 5px solid #fcaf3e;
+}
+
+div.sidebar li.toctree-l1.current li.toctree-l2 a {
+ border-right: none;
+}
+
+
+/* Footer */
+
+div.footer-wrapper {
+ background: url(bgfooter.png) top left repeat-x;
+ border-top: 4px solid #babdb6;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ min-height: 80px;
+}
+
+div.footer, div.footer a {
+ color: #888a85;
+}
+
+div.footer .right {
+ text-align: right;
+}
+
+div.footer .left {
+ text-transform: uppercase;
+}
+
+
+/* Styles copied form basic theme */
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li div.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable dl, table.indextable dd {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css
new file mode 100644
index 0000000..45bae6e
--- /dev/null
+++ b/docs/_static/pygments.css
@@ -0,0 +1,69 @@
+.hll { background-color: #ffffcc }
+.c { color: #8f5902; font-style: italic } /* Comment */
+.err { color: #a40000; border: 1px solid #ef2929 } /* Error */
+.g { color: #000000 } /* Generic */
+.k { color: #204a87; font-weight: bold } /* Keyword */
+.l { color: #000000 } /* Literal */
+.n { color: #000000 } /* Name */
+.o { color: #ce5c00; font-weight: bold } /* Operator */
+.x { color: #000000 } /* Other */
+.p { color: #000000; font-weight: bold } /* Punctuation */
+.cm { color: #8f5902; font-style: italic } /* Comment.Multiline */
+.cp { color: #8f5902; font-style: italic } /* Comment.Preproc */
+.c1 { color: #8f5902; font-style: italic } /* Comment.Single */
+.cs { color: #8f5902; font-style: italic } /* Comment.Special */
+.gd { color: #a40000 } /* Generic.Deleted */
+.ge { color: #000000; font-style: italic } /* Generic.Emph */
+.gr { color: #ef2929 } /* Generic.Error */
+.gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.gi { color: #00A000 } /* Generic.Inserted */
+.go { color: #000000; font-style: italic } /* Generic.Output */
+.gp { color: #8f5902 } /* Generic.Prompt */
+.gs { color: #000000; font-weight: bold } /* Generic.Strong */
+.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.gt { color: #a40000; font-weight: bold } /* Generic.Traceback */
+.kc { color: #204a87; font-weight: bold } /* Keyword.Constant */
+.kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */
+.kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */
+.kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */
+.kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */
+.kt { color: #204a87; font-weight: bold } /* Keyword.Type */
+.ld { color: #000000 } /* Literal.Date */
+.m { color: #0000cf; font-weight: bold } /* Literal.Number */
+.s { color: #4e9a06 } /* Literal.String */
+.na { color: #c4a000 } /* Name.Attribute */
+.nb { color: #204a87 } /* Name.Builtin */
+.nc { color: #000000 } /* Name.Class */
+.no { color: #000000 } /* Name.Constant */
+.nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */
+.ni { color: #ce5c00 } /* Name.Entity */
+.ne { color: #cc0000; font-weight: bold } /* Name.Exception */
+.nf { color: #000000 } /* Name.Function */
+.nl { color: #f57900 } /* Name.Label */
+.nn { color: #000000 } /* Name.Namespace */
+.nx { color: #000000 } /* Name.Other */
+.py { color: #000000 } /* Name.Property */
+.nt { color: #204a87; font-weight: bold } /* Name.Tag */
+.nv { color: #000000 } /* Name.Variable */
+.ow { color: #204a87; font-weight: bold } /* Operator.Word */
+.w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */
+.mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */
+.mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */
+.mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */
+.mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */
+.sb { color: #4e9a06 } /* Literal.String.Backtick */
+.sc { color: #4e9a06 } /* Literal.String.Char */
+.sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */
+.s2 { color: #4e9a06 } /* Literal.String.Double */
+.se { color: #4e9a06 } /* Literal.String.Escape */
+.sh { color: #4e9a06 } /* Literal.String.Heredoc */
+.si { color: #4e9a06 } /* Literal.String.Interpol */
+.sx { color: #4e9a06 } /* Literal.String.Other */
+.sr { color: #4e9a06 } /* Literal.String.Regex */
+.s1 { color: #4e9a06 } /* Literal.String.Single */
+.ss { color: #4e9a06 } /* Literal.String.Symbol */
+.bp { color: #3465a4 } /* Name.Builtin.Pseudo */
+.vc { color: #000000 } /* Name.Variable.Class */
+.vg { color: #000000 } /* Name.Variable.Global */
+.vi { color: #000000 } /* Name.Variable.Instance */
+.il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */
diff --git a/docs/change-license-emails.txt b/docs/change-license-emails.txt
new file mode 100644
index 0000000..7086089
--- /dev/null
+++ b/docs/change-license-emails.txt
@@ -0,0 +1,272 @@
+From: intrigeri <intrigeri@boum.org>
+To: Isis! <isis@patternsinthevoid.net>
+Subject: AGPL library, really?
+Date: Thu, 04 Jul 2013 17:38:46 +0000
+
+Hi isis,
+
+I see on https://pypi.python.org/pypi/gnupg that you released this
+library under AGPLv3. Is this correct?
+
+If it is, then you might be interested to have a look to this long
+ongoing thread on debian-devel mailing-list where I've seen explained
+(by people I trust on this topic) that AGPLv3 is really not well
+suited for libraries -- to start with, quite some of its terms are
+ambiguous when one tries to apply them to a library:
+https://lists.debian.org/debian-devel/2013/07/msg00031.html
+
+Cheers,
+--
+ intrigeri
+ | GnuPG key @ https://gaffer.ptitcanardnoir.org/intrigeri/intrigeri.asc
+ | OTR fingerprint @ https://gaffer.ptitcanardnoir.org/intrigeri/otr.asc
+
+
+From: isis agora lovecruft <isis@patternsinthevoid.net>
+To: intrigeri <intrigeri@boum.org>
+Subject: Re: AGPL library, really?
+Date: Sun, 07 Jul 2013 04:20:13 +0000
+
+Hi intrigeri!
+
+intrigeri transcribed 2.3K bytes:
+> I see on https://pypi.python.org/pypi/gnupg that you released this
+> library under AGPLv3. Is this correct?
+
+Yes, that it correct.
+
+> If it is, then you might be interested to have a look to this long
+> ongoing thread on debian-devel mailing-list where I've seen explained
+> (by people I trust on this topic) that AGPLv3 is really not well
+> suited for libraries -- to start with, quite some of its terms are
+> ambiguous when one tries to apply them to a library:
+> https://lists.debian.org/debian-devel/2013/07/msg00031.html
+
+Okay, thanks!
+
+/me reads…
+
+I think this message better describes why AGPL is bad for libraries:
+https://lists.debian.org/debian-devel/2013/07/msg00041.html or, at least, I
+understood that one better than the first.
+
+I certainly do not want to make problems for Debian, and now that a bunch of
+Tor, LEAP, CryptoParty, and Freebox projects, and perhaps soon Pip too, will
+be depending on this, I *really* don't want to make anyone else's license hell
+worse.
+
+Attached is an email from leap@lists.riseup.net where we had fisticuffs over
+licensing opinions, wherein I explained my preference for AGPL for
+everything. Essentially, I do not want people/corporations/etc. to use my work
+in a closed source application and then potentially make changes to patch
+found vulnerabilities without contributing those patches back to the main
+codebase.
+
+Though, you're correct, this doesn't make sense for a library, as a
+closed-source web-service frontend to this Python module likely isn't going to
+get anyone exploited except the person running the service. So it doesn't make
+as much sense.
+
+Do you know if it is okay for me to re-license it as regular GPL?
+
+Do you have any advice on which of GPLv(2|3)(\+)* that I should use?
+
+Thanks for pointing this out so quickly before it caused trouble, by the
+way. :)
+
+--
+ ♥Ⓐisis agora lovecruft
+_________________________________________________________
+GPG: 4096R/A3ADB67A2CDB8B35
+Current Keys: https://blog.patternsinthevoid.net/isis.txt
+
+--Attachment 1--
+ Date: Tue, 28 May 2013 04:13:56 +0000
+ From: isis agora lovecruft <isis@patternsinthevoid.net>
+ To: micah <micah@riseup.net>
+ Cc: leap@lists.riseup.net
+ X-GPG-Public-Key-URL: https://blog.patternsinthevoid.net/isis.txt
+ X-Louis-Lingg: In this hope do I say to you I despise you. I despise your
+ order, your laws, your force-propped authority. Hang me for it!
+ Subject: Re: [leap] license
+
+ micah transcribed 1.3K bytes:
+ > Tomas Touceda <chiiph@riseup.net> writes:
+ >
+ > > On 05/13/2013 05:32 PM, elijah wrote:
+ > >> if you have any wisdom or opinions regarding the ever joyful and
+ > >> uncontroversial topic of free software licenses, then please deposit
+ > >> said wisdom or opinions in this wiki:
+ > >>
+ > >> https://we.riseup.net/leap/license
+ > >>
+ > >> in a nutshell, we need to decide on a license for the client.
+ > >
+ > > Does anybody have license knowledge a priori? Or should I get started
+ > > reading licenses?
+ >
+ > I'm supposed to have a more than zero knowledge of what constitutes free
+ > licenses due to my debian training, and debian is world-renknowned for
+ > having a particularly nasty debian-legal mailing list where licenses are
+ > chewed up and spit out... but I personally hate the topic and tend to
+ > avoid it as much as possible.
+ >
+ > So basically my opinons are:
+ >
+ > 1. no license that is incompatible with the DFSG[0] (debian free
+ > software guidelines) - it seems like we are probably in agreement about
+ > this?
+
+ ACK
+
+ > 2. BSD multi-claused licenses and MIT are confusing and annoying, so I
+ > tend to think they should be avoided due to this
+ >
+
+ ACK
+
+ > 3. openssl derived works require granting an exception with GPL licenses
+ > (an exception is trivial), so I prefer gnutls code where possible
+ >
+
+ ACK
+
+ > 4. it seems weird to make things AGPL that aren't webapps
+ >
+
+ I started release everything I could AGPLv3 three years ago, after a
+ conversation with some other activist free-software devs:
+
+ Me: "I want a license which says 'If you are part of any governing body or
+ corporation which contracts to any private or public military entity, then
+ you should go fuck youself. And no, you cannot use my software -- I will
+ sue your pants off.'"
+
+ Them: "Isis, that is silly, and even na=C3=AFve. Universities are libraries are
+ often 'part of governing bodies', you don't want to exclude them, do you?
+ And also, you're like not going to see the blobs your code is included
+ in...it will get privately installed on custom military and law
+ enforcement hardware, and when they're done with it it'll go and rot
+ outside on a base or in a police confiscation parking lot somewhere."
+
+ Me: "Hum. I hate talking about licenses anyway."
+
+ Them: "Yeah, it sucks. But it's important for us to take this seriously,
+ because the tools we're working on have the potential for helping us
+ better organise at protests, as well as better help the cops kettle us
+ into paddy wagons." [one of the tools was a crisis mapping thing]
+
+ Different one of them: "Perhaps you both should read AGPL, and see if that
+ helps. I don't think using law against them is going to work, because we
+ can't assume they will play by the rules, but if we're arguing licenses
+ anyway..."
+
+ AGPL also seems useful when it seems possible that shady closed-source
+ startups are going to add a fancier UI or other feature to your code, and then
+ market it. This is especially worrying, not because they are "stealing users",
+ but because it's never clear if vulns discovered in your own code have been
+ fixed in theirs and vice versa. Or, it could get used in way that is
+ dangerous, or that it wasn't meant for. (For example, there is currently a
+ concern that a certain shell company is going to use OONI's code on these
+ little android-system-on-a-USB dongly thingies...and there are certain dangers
+ with Tor on Android that these people either don't understand or have no
+ intention of warning users about.)
+
+ Anyway. There is my argument for AGPL.
+
+ Though I also hate these discussions, don't care about laws, think reformism
+ is bunk, WTFPL is the only sane LICENSE, and all that jazz, so I'm going to go
+ stand over there ----------------------------------------------------------->
+ and watch everybody else duke it out. :)
+
+ --
+ ♥Ⓐ isis agora lovecruft
+ _________________________________________________________
+ GPG: 4096R/A3ADB67A2CDB8B35
+ Current Keys: https://blog.patternsinthevoid.net/isis.txt
+--End Attachment 1--
+
+From: intrigeri <intrigeri@boum.org>
+To: Isis! <isis@patternsinthevoid.net>
+Subject: Re: AGPL library, really?
+Date: Tue, 09 Jul 2013 18:30:46 +0000
+
+Hi isis,
+
+isis agora lovecruft wrote (07 Jul 2013 04:20:13 GMT) :
+> I think this message better describes why AGPL is bad for libraries:
+> https://lists.debian.org/debian-devel/2013/07/msg00041.html
+> or, at least, I understood that one better than the first.
+
+TBH, I've pointed you at the beginning of the thread because I was too
+lazy to go fetch the best email in there. I'm glad it helps anyway.
+
+> Do you know if it is okay for me to re-license it as regular GPL?
+
+I've just re-read a bit to confirm, and my conclusion is that: yeah,
+as the sole copyright holder (is this the case?) you can freely
+re-licence to whatever you want.
+
+> Do you have any advice on which of GPLv(2|3)(\+)* that I should use?
+
+I usually do GPL-3+, but I would not be able to defend it seriously
+against v2 or v2+.
+
+> Thanks for pointing this out so quickly before it caused trouble, by the
+> way. :)
+
+Np.
+
+Cheers!
+--
+ intrigeri
+ | GnuPG key @ https://gaffer.ptitcanardnoir.org/intrigeri/intrigeri.asc
+ | OTR fingerprint @ https://gaffer.ptitcanardnoir.org/intrigeri/otr.asc
+
+From: isis agora lovecruft <isis@patternsinthevoid.net>
+To: intrigeri <intrigeri@boum.org>
+Subject: Re: AGPL library, really?
+Date: Thu, 11 Jul 2013 09:24:12 +0000
+
+intrigeri transcribed 2.6K bytes:
+> isis agora lovecruft wrote (07 Jul 2013 04:20:13 GMT) :
+> > Do you know if it is okay for me to re-license it as regular GPL?
+>
+> I've just re-read a bit to confirm, and my conclusion is that: yeah,
+> as the sole copyright holder (is this the case?) you can freely
+> re-licence to whatever you want.
+
+Hey intrigeri,
+
+I've decided to re-license with your recommendation of GPL3+. Is it okay to
+credit you and/or publicly point to these emails as the basis for the
+rationale for the switch?
+
+--
+ ♥Ⓐ isis agora lovecruft
+_________________________________________________________
+GPG: 4096R/A3ADB67A2CDB8B35
+Current Keys: https://blog.patternsinthevoid.net/isis.txt
+
+From: intrigeri <intrigeri@boum.org>
+To: Isis! <isis@patternsinthevoid.net>
+Subject: Re: AGPL library, really?
+Date: Sun, 14 Jul 2013 22:33:35 +0000
+
+Hi isis,
+
+> Is it okay to credit you and/or publicly point to these emails as
+> the basis for the rationale for the switch?
+
+Feel free to credit me if you wish, but I certainly don't feel it's
+necessary.
+
+I feel a bit lazy to read this thread again to check if it's fine to
+publish stuff from there, so if you don't mind, I'd rather skip this
+part ;)
+
+Cheers,
+--
+ intrigeri
+ | GnuPG key @ https://gaffer.ptitcanardnoir.org/intrigeri/intrigeri.asc
+ | OTR fingerprint @ https://gaffer.ptitcanardnoir.org/intrigeri/otr.asc
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..4986e0b
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,312 @@
+# -*- coding: utf-8 -*-
+#
+# python-gnupg documentation build configuration file, created by
+# sphinx-quickstart on Fri Apr 5 22:38:47 2013.
+#
+# 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 sys, os
+import psutil
+
+# 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('./../'))
+sys.path.insert(0, os.path.abspath('.'))
+
+# -- Autodoc settings ----------------------------------------------------------
+## trying to set this somewhere...
+autodoc_member_order = 'bysource'
+autodoc_default_flags = ['members', 'show-inheritance', 'undoc-members', 'show-hidden']
+autoclass_content = 'both'
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+needs_sphinx = '1.1'
+
+# 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.viewcode',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.doctest',
+ 'sphinxcontrib.fulltoc',
+ ]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_static']
+
+# 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'gnupg'
+copyright = u'2013, Isis Agora Lovecruft'
+
+# 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.
+#
+# The short X.Y version.
+import gnupg
+version = gnupg.__version__
+# The full version, including alpha/beta/rc tags.
+release = gnupg.__version__
+
+# 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 = '%d %B %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 = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'monokai'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
+
+# -- 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 = 'default'
+html_theme = 'scrolls'
+#html_theme = 'traditional'
+#html_theme = 'nature'
+#html_theme = 'pyramid'
+html_theme = 'agogo'
+#html_theme = 'haiku'
+# 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.
+html_theme_options = {
+# 'stickysidebar': 'true',
+# 'rightsidebar':'true',
+ 'nosidebar': 'false',
+# 'full_logo': 'false'
+ 'sidebarwidth': '300'
+ }
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['_static']
+
+# 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 = "gnupg: Python Module Documentation"
+
+# 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 = '%A, %d %B %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 = {}
+
+# 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 = '.html'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'gnupgdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'python-gnupg.tex', u'python-gnupg Documentation',
+ u'Isis Agora Lovecruft', '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
+
+# 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', 'gnupg Python Module Docs', u'gnupg Python Module Documentation',
+ [u'Isis Agora Lovecruft'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'python-gnupg', u'python-gnupg Documentation',
+ u'Isis Agora Lovecruft', 'python-gnupg', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+
+# -- Options for Epub output ---------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'python-gnupg'
+epub_author = u'Isis Agora Lovecruft'
+epub_publisher = u'Isis Agora Lovecruft'
+epub_copyright = u'2013, Isis Agora Lovecruft'
+
+# 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 = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# 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
diff --git a/docs/gnupg.rst b/docs/gnupg.rst
new file mode 100644
index 0000000..fc8a479
--- /dev/null
+++ b/docs/gnupg.rst
@@ -0,0 +1,131 @@
+gnupg package
+=============
+
+gnupg module
+------------
+
+This module contains public classes for working with GnuPG_. To get started,
+do:
+
+>>> import gnupg
+>>> gpg = gnupg.GPG()
+
+
+.. automodule:: gnupg
+ :members:
+ :undoc-members:
+ :private-members:
+ :show-inheritance:
+
+.. _meta:
+
+meta module
+-----------
+
+Contains the meta and base classes which :class:`gnupg.GPG` inherits
+from. Mostly, you shouldn't ever need to touch anything in here, unless you're
+doing some serious hacking.
+
+
+.. automodule:: gnupg._meta
+ :members:
+ :private-members:
+ :special-members:
+ :exclude-members: _agent_proc, __module__, __dict__, _decode_errors, init,
+ __weakref__, _result_map, __metaclass__
+ :show-inheritance:
+
+.. _parsers:
+
+parsers module
+--------------
+
+These are classes for parsing both user inputs and status file descriptor
+flags from GnuPG's output. The latter are used in order to determine what our
+GnuPG process is doing and retrieve information about its operations, which
+are stored in corresponding classes in
+:attr:`~gnupg._meta.GPGBase._result_map`. Some status flags aren't handled yet
+-- information on *all* of the flags (well, at least the documented ones…) can
+be found in the :file:`docs/DETAILS` file in GnuPG's source_, which has been
+included here_ as well.
+
+
+.. automodule:: gnupg._parsers
+ :members:
+ :undoc-members:
+ :private-members:
+ :show-inheritance:
+
+
+.. _util:
+
+util module
+-----------
+
+You shouldn't really need to mess with this module either, it mostly deals
+with low-level IO and file handling operations, de-/en- coding issues, and
+setting up basic package facilities such as logging.
+
+.. automodule:: gnupg._util
+ :members:
+ :undoc-members:
+ :private-members:
+ :show-inheritance:
+
+
+About this fork
+---------------
+
+This is a modified version of python-gnupg_, (forked from version 0.3.2) which
+was created by Vinay Sajip, which itself is a modification of GPG.py written
+by Steve Traugott, which in turn is a modification of the pycrypto GnuPG
+interface written by A.M. Kuchling.
+
+This version is patched to sanitize untrusted inputs, due to the necessity of
+executing ``subprocess.Popen([...], shell=True)`` in order to communicate with
+GnuPG. Several speed improvements were also made based on code profiling, and
+the API has been cleaned up to support an easier, more Pythonic, interaction.
+
+
+Previous Authors' Documentation
+-------------------------------
+
+Steve Traugott's documentation:
+ |
+ | 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 'gpghome' 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
+
+
+Vinay Sajip's documentation:
+ |
+ | 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.
+ |
+ | A unittest harness (test_gnupg.py) has also been added.
+ |
+ | Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved.
+
+
+.. _GnuPG: http://gnupg.org
+.. _python-gnupg: https://code.google.com/p/python-gnupg/
+.. _source: http://http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=shortlog;h=refs/heads/master
+.. _here: ./_static/DETAILS.html
diff --git a/docs/gpg-migrate.txt b/docs/gpg-migrate.txt
new file mode 100644
index 0000000..1d07450
--- /dev/null
+++ b/docs/gpg-migrate.txt
@@ -0,0 +1,208 @@
+# taken from http://atom.smasher.org/gpg/gpg-migrate.txt on 8 Aug 2013
+
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+HOW TO MIGRATE A (SUB)KEY INTO A NEW KEY
+
+this document [is intended to] explain how to migrate a key or subkey from
+one OpenPGP key into another OpenPGP key.
+
+here i'll walk you through the steps of migrating my old primary signing
+key (3D7D41E3) into my new key (D9F57808). the process of migrating
+encryption (and signing) subkeys is nearly identical. for the adventurous,
+you can even migrate several keys at once using this method (i recommend
+going through it once or twice with only one key).
+
+============================================================================
+
+doing this requires:
+ 1) basic knowledge of a *nix command line and how to use it
+ 2) advanced knowledge of gpg and how to use it
+ 3) common sense (BACKUP YOUR DATA!!)
+
+please note:
+ * this works for me. that does not necessarily mean that it will
+ work for you
+ * if you screw something up, it's *YOUR* problem, not mine
+ * this was tested with GnuPG 1.2.4, and written on or about
+ 12 May 2004
+ * updates, if there are any, will probably be noted above
+ * comments and suggestions about this tutorial should be sent to:
+ <atom {at} smasher.org>
+ * questions about gpg should be sent to the gnupg-users mailing
+ list: http://lists.gnupg.org/mailman/listinfo/gnupg-users
+
+============================================================================
+
+* old key: 3EBE 2810 30AE 601D 54B2 4A90 9C28 0BBF 3D7D 41E3
+
+pub 1024D/3D7D41E3 2003-10-04 Atom Smasher <atom@suspicious.org>
+uid Atom Smasher <atom@smasher.org>
+sub 2048g/1E88BF71 2003-10-04 [expires: 2005-01-26]
+
+================
+
+* new key: 762A 3B98 A3C3 96C9 C6B7 582A B88D 52E4 D9F5 7808
+
+pub 4096R/D9F57808 2004-05-11 Atom Smasher <atom@smasher.org>
+uid Atom Smasher <atom@suspicious.org>
+sub 1024D/3D7D41E3 2003-10-04 [expires: 2006-02-13]
+sub 2048g/1E88BF71 2003-10-04 [expires: 2006-01-26]
+
+===================================
+
+backup the new keys:
+ $ gpg --export D9F57808 > D9F57808_original.txt
+ $ gpg --export-secret-key D9F57808 > D9F57808_original_secret.txt
+
+and the old keys:
+ $ gpg --export 3D7D41E3 > 3D7D41E3_original.txt
+ $ gpg --export-secret-key 3D7D41E3 > 3D7D41E3_original_secret.txt
+
+break the old secret key into pieces:
+ $ gpg --export-secret-key 3D7D41E3 | gpgsplit -vp OLD_SEC
+
+===================================
+
+in this case, the old primary key needs to be converted into a subkey.
+pgpdump shows that it's a primary key:
+ $ pgpdump OLD_SEC000001-005.secret_key
+ Old: Secret Key Packet(tag 5)
+ <<snip>>
+
+only do this if you're converting a PRIMARY KEY into a SUBKEY: open that
+file in a hex editor and refer to RFC2440 4.2 & 4.3. i recommend setting
+the hex editor into a binary display. in this example the first byte is
+"10010101" and it needs to be changed to "10011101". the change can be
+confirmed with pgpdump:
+ $ pgpdump OLD_SEC000001-005.secret_key
+ Old: Secret Subkey Packet(tag 7)
+ <<snip>>
+
+we've now converted the old primary key into a subkey. if you're moving a
+subkey from one key to another, you don't have to do that.
+
+===================================
+
+use "edit-key" and add a subkey of the same type (DSA, for this example)
+and size to the new key. this subkey will be discarded, but we need to
+generate it for now: this seems to be the quickest way to generate a
+keybinding signature with the correct features.
+
+exit from "edit-key" and save.
+
+===================================
+
+split the current version of the new public key:
+ $ gpg --export D9F57808 | gpgsplit -vp TEMP_KEY1
+the only part we need from that split is the binding signature that was
+just generated.
+
+delete (from the keyring) both the private and public copies of the new
+key:
+ $ gpg --delete-secret-key D9F57808
+ $ gpg --delete-key D9F57808
+
+also delete (from the keyring) both the private and public copies of the
+old key.
+
+because the subkey that we're adding to the new key does not correspond to
+the subkey binding signature that was created for it, gpg will not allow
+the key to be imported. the way around that is to "force feed" the key into
+the keyring, bypassing the normal sanity checks. once it's in the keyring
+we can make it all work.
+
+* note: the actual file names that you're using may differ somewhat from
+mine. when in doubt (or rather, when you're not sure), use pgpdump to
+examine the contents of files.
+
+~import~ (directly into the secret keyring) the original copy of the new
+key (D9F57808_original_secret.txt), the edited copy of the old primary key
+(now a subkey, OLD_SEC000001-005.secret_key) and the binding signature of
+the subkey that we just generated (TEMP_KEY1000007-002.sig):
+ $ cat D9F57808_original_secret.txt \
+ OLD_SEC000001-005.secret_key \
+ TEMP_KEY1000007-002.sig >> ~/.gnupg/secring.gpg
+
+~import~ (directly into the public keyring) a public key by adding
+"| gpgsplit --no-split --secret-to-public" to the above command like this:
+ $ cat D9F57808_original_secret.txt \
+ OLD_SEC000001-005.secret_key \
+ TEMP_KEY1000007-002.sig \
+ | gpgsplit --no-split --secret-to-public >> ~/.gnupg/pubring.gpg
+
+now we have to make a *valid* keybinding signature for the subkey that we
+just added. use "edit-key", select the newly added subkey, and reset it's
+expiration date. that will generate a valid keybinding signature.
+
+while in "edit-key", reset the password. otherwise you may inadvertently
+create a key with multiple passwords, as described here -
+ http://atom.smasher.org/gpg/gpg-passwords.txt
+
+exit from "edit-key" and save.
+
+=========================
+
+this last part makes no sense to me (but it doesn't seem to work
+otherwise).
+
+back up (export) the latest version of the public and private keys:
+ $ gpg --export-secret-key D9F57808 > new-key.sec
+ $ gpg --export D9F57808 > new-key.pub
+
+delete (from the keyring) the private and public key:
+ $ gpg --delete-secret-key D9F57808
+ $ gpg --delete-key D9F57808
+
+also, delete (from the keyring) all copies of the old (sub)key that was
+just added to the new key.
+
+import the new public and private keys:
+ $ gpg --import new-key.sec new-key.pub
+
+=========================
+
+before you publish your new key:
+
+* make sure the key is "ultimately trusted". deleting and importing will
+have removed it from the trust db. since you own the key, ultimate trust
+seems reasonable.
+
+* check all expiration dates and preferences. some of these operations may
+have changed your expiration dates and preferences; reset as necessary.
+
+* test out all key components for creating and verifying signatures, and
+encryption/decryption. use the bang (!) to force each (sub)key:
+ create & verify signatures:
+ $ date | gpg -u 'D9F57808!' --clearsign | gpg -v --verify
+ $ date | gpg -u '3D7D41E3!' --clearsign | gpg -v --verify
+ encrypt/decrypt:
+ $ date | gpg -ear 'D9F57808!' | gpg -v --decrypt
+ $ date | gpg -ear '1E88BF71!' | gpg -v --decrypt
+
+* after testing out the keys locally, send your new public key to one or
+two people and test all key components (sending signed/encrypted messages
+to each other using all key components). make sure that they first delete
+(from their keyrings) your old key! and make sure that they understand that
+the key should NOT be circulated until all functions are verified to be
+working!
+
+* when putting the new key into circulation, it's probably a good idea to
+expire/revoke the old key. include a revocation comment that specifies the
+new key ID and instructions to delete the old key from the keyring.
+
+* note on key revocation: according to the OpenPGP standards a revocation
+generated by a sub key will be ignored, unless that subkey has been
+designated (by the primary key) as a revocation key. GnuPG seems to behave
+correctly, but some versions of PGP(tm) may not. if someone is claiming
+that your new key is revoked, have then remove all of your old and current
+keys from their keyring: then re-import your current key(s).
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.4 (FreeBSD)
+
+iD8DBQFApwlpnCgLvz19QeMRAhrpAJ4rhLrmVDjABh8CpPdTZ5jNMi7LsgCgp35S
+6qcUe4csx1p5AE2rAsvDi9c=
+=y7bA
+-----END PGP SIGNATURE-----
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..c266353
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,44 @@
+.. gnupg documentation master file, created by
+ sphinx-quickstart on Fri Apr 5 22:38:47 2013.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+gnupg: Python Package Documentation
+===================================
+A Python interface for handling interactions with GnuPG, including keyfile
+generation, keyring maintainance, import and export, encryption and
+decryption, sending to and recieving from keyservers, and signing and
+verification.
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ gnupg
+
+
+Source, license, & bug reports
+==============================
+The source code which was used to generate this documentation is accessible by
+clicking the little `source` links next to the docs. Current source code can
+be found in this github repository_. The **master** branch always reflects the
+latest release, all releases are tagged with signed, annotated git tags, and
+the **develop** branch represents the state of the next release.
+
+This package is released under GPLv3_ or greater.
+
+If you find a bug, or would like to request a feature, please use our public
+bugtracker_ on github. Patches warmly welcome.
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
+.. _source: https://github.com/isislovecruft/python-gnupg
+.. _repository: https://github.com/isislovecruft/python-gnupg
+.. _GPLv3: https://www.gnu.org/licenses/gpl.txt
+.. _bugtracker: https://github.com/isislovecruft/python-gnupg/issues
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..fa3950f
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,190 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :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. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over 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
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-gnupg.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-gnupg.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/gnupg/__init__.py b/gnupg/__init__.py
new file mode 100644
index 0000000..5c1430c
--- /dev/null
+++ b/gnupg/__init__.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+from __future__ import absolute_import
+
+from . import gnupg
+from . import copyleft
+from . import _ansistrm
+from . import _logger
+from . import _meta
+from . import _parsers
+from . import _util
+from .gnupg import GPG
+from ._version import get_versions
+
+__version__ = get_versions()['version']
+__authors__ = copyleft.authors
+__license__ = copyleft.full_text
+__copyleft__ = copyleft.copyright
+
+## do not set __package__ = "gnupg", else we will end up with
+## gnupg.<*allofthethings*>
+__all__ = ["GPG", "_util", "_parsers", "_meta", "_logger"]
+
+## avoid the "from gnupg import gnupg" idiom
+del gnupg
+del absolute_import
+del copyleft
+del get_versions
+del _version
diff --git a/gnupg/_ansistrm.py b/gnupg/_ansistrm.py
new file mode 100644
index 0000000..cfd50a1
--- /dev/null
+++ b/gnupg/_ansistrm.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python wrapper aroung GnuPG, and it was
+# taken from https://gist.github.com/vsajip/758430 on the 14th of May, 2013. It
+# has also been included in the 'logutils' Python module, see
+# https://code.google.com/p/logutils/ .
+#
+# The original copyright and license text are as follows:
+# |
+# | Copyright (C) 2010-2012 Vinay Sajip. All rights reserved.
+# | Licensed under the new BSD license.
+# |
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+import ctypes
+import logging
+import os
+
+class ColorizingStreamHandler(logging.StreamHandler):
+ # color names to indices
+ color_map = {
+ 'black': 0,
+ 'red': 1,
+ 'green': 2,
+ 'yellow': 3,
+ 'blue': 4,
+ 'magenta': 5,
+ 'cyan': 6,
+ 'white': 7,
+ }
+
+ #levels to (background, foreground, bold/intense)
+ if os.name == 'nt':
+ level_map = {
+ logging.DEBUG: (None, 'blue', True),
+ logging.INFO: (None, 'green', False),
+ logging.WARNING: (None, 'yellow', True),
+ logging.ERROR: (None, 'red', True),
+ logging.CRITICAL: ('red', 'white', True),
+ }
+ else:
+ level_map = {
+ logging.DEBUG: (None, 'blue', False),
+ logging.INFO: (None, 'green', False),
+ logging.WARNING: (None, 'yellow', False),
+ logging.ERROR: (None, 'red', False),
+ logging.CRITICAL: ('red', 'white', True),
+ }
+ csi = '\x1b['
+ reset = '\x1b[0m'
+
+ @property
+ def is_tty(self):
+ isatty = getattr(self.stream, 'isatty', None)
+ return isatty and isatty()
+
+ def emit(self, record):
+ try:
+ message = self.format(record)
+ stream = self.stream
+ if not self.is_tty:
+ stream.write(message)
+ else:
+ self.output_colorized(message)
+ stream.write(getattr(self, 'terminator', '\n'))
+ self.flush()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handleError(record)
+
+ if os.name != 'nt':
+ def output_colorized(self, message):
+ self.stream.write(message)
+ else:
+ import re
+ ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m')
+
+ nt_color_map = {
+ 0: 0x00, # black
+ 1: 0x04, # red
+ 2: 0x02, # green
+ 3: 0x06, # yellow
+ 4: 0x01, # blue
+ 5: 0x05, # magenta
+ 6: 0x03, # cyan
+ 7: 0x07, # white
+ }
+
+ def output_colorized(self, message):
+ parts = self.ansi_esc.split(message)
+ write = self.stream.write
+ h = None
+ fd = getattr(self.stream, 'fileno', None)
+ if fd is not None:
+ fd = fd()
+ if fd in (1, 2): # stdout or stderr
+ h = ctypes.windll.kernel32.GetStdHandle(-10 - fd)
+ while parts:
+ text = parts.pop(0)
+ if text:
+ write(text)
+ if parts:
+ params = parts.pop(0)
+ if h is not None:
+ params = [int(p) for p in params.split(';')]
+ color = 0
+ for p in params:
+ if 40 <= p <= 47:
+ color |= self.nt_color_map[p - 40] << 4
+ elif 30 <= p <= 37:
+ color |= self.nt_color_map[p - 30]
+ elif p == 1:
+ color |= 0x08 # foreground intensity on
+ elif p == 0: # reset to default color
+ color = 0x07
+ else:
+ pass # error condition ignored
+ ctypes.windll.kernel32.SetConsoleTextAttribute(h, color)
+
+ def colorize(self, message, record):
+ if record.levelno in self.level_map:
+ bg, fg, bold = self.level_map[record.levelno]
+ params = []
+ if bg in self.color_map:
+ params.append(str(self.color_map[bg] + 40))
+ if fg in self.color_map:
+ params.append(str(self.color_map[fg] + 30))
+ if bold:
+ params.append('1')
+ if params:
+ message = ''.join((self.csi, ';'.join(params),
+ 'm', message, self.reset))
+ return message
+
+ def format(self, record):
+ message = logging.StreamHandler.format(self, record)
+ if self.is_tty:
+ # Don't colorize any traceback
+ parts = message.split('\n', 1)
+ parts[0] = self.colorize(parts[0], record)
+ message = '\n'.join(parts)
+ return message
+
+def main():
+ root = logging.getLogger()
+ root.setLevel(logging.DEBUG)
+ root.addHandler(ColorizingStreamHandler())
+ logging.debug('DEBUG')
+ logging.info('INFO')
+ logging.warning('WARNING')
+ logging.error('ERROR')
+ logging.critical('CRITICAL')
+
+if __name__ == '__main__':
+ main()
diff --git a/gnupg/_logger.py b/gnupg/_logger.py
new file mode 100644
index 0000000..870617e
--- /dev/null
+++ b/gnupg/_logger.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+'''Logging module for python-gnupg.'''
+
+from __future__ import absolute_import
+from __future__ import print_function
+from datetime import datetime
+from functools import wraps
+
+import logging
+import sys
+import os
+
+try:
+ from logging import NullHandler
+except:
+ class NullHandler(logging.Handler):
+ def handle(self, record):
+ pass
+
+from . import _ansistrm
+
+GNUPG_STATUS_LEVEL = 9
+
+def status(self, message, *args, **kwargs):
+ """LogRecord for GnuPG internal status messages."""
+ if self.isEnabledFor(GNUPG_STATUS_LEVEL):
+ self._log(GNUPG_STATUS_LEVEL, message, args, **kwargs)
+
+@wraps(logging.Logger)
+def create_logger(level=logging.NOTSET):
+ """Create a logger for python-gnupg at a specific message level.
+
+ :type level: :obj:`int` or :obj:`str`
+ :param level: A string or an integer for the lowest level to include in
+ logs.
+
+ **Available levels:**
+
+ ==== ======== ========================================
+ int str description
+ ==== ======== ========================================
+ 0 NOTSET Disable all logging.
+ 9 GNUPG Log GnuPG's internal status messages.
+ 10 DEBUG Log module level debuging messages.
+ 20 INFO Normal user-level messages.
+ 30 WARN Warning messages.
+ 40 ERROR Error messages and tracebacks.
+ 50 CRITICAL Unhandled exceptions and tracebacks.
+ ==== ======== ========================================
+ """
+ _test = os.path.join(os.path.join(os.getcwd(), 'gnupg'), 'test')
+ _now = datetime.now().strftime("%Y-%m-%d_%H%M%S")
+ _fn = os.path.join(_test, "%s_test_gnupg.log" % _now)
+ _fmt = "%(relativeCreated)-4d L%(lineno)-4d:%(funcName)-18.18s %(levelname)-7.7s %(message)s"
+
+ ## Add the GNUPG_STATUS_LEVEL LogRecord to all Loggers in the module:
+ logging.addLevelName(GNUPG_STATUS_LEVEL, "GNUPG")
+ logging.Logger.status = status
+
+ if level > logging.NOTSET:
+ logging.basicConfig(level=level, filename=_fn,
+ filemode="a", format=_fmt)
+ logging.logThreads = True
+ if hasattr(logging,'captureWarnings'):
+ logging.captureWarnings(True)
+ colouriser = _ansistrm.ColorizingStreamHandler
+ colouriser.level_map[9] = (None, 'blue', False)
+ colouriser.level_map[10] = (None, 'cyan', False)
+ handler = colouriser(sys.stderr)
+ handler.setLevel(level)
+
+ formatr = logging.Formatter(_fmt)
+ handler.setFormatter(formatr)
+ else:
+ handler = NullHandler()
+
+ log = logging.getLogger('gnupg')
+ log.addHandler(handler)
+ log.setLevel(level)
+ log.info("Log opened: %s UTC" % datetime.ctime(datetime.utcnow()))
+ return log
diff --git a/gnupg/_meta.py b/gnupg/_meta.py
new file mode 100644
index 0000000..f11310c
--- /dev/null
+++ b/gnupg/_meta.py
@@ -0,0 +1,871 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+'''Meta and base classes for hiding internal functions, and controlling
+attribute creation and handling.
+'''
+
+from __future__ import absolute_import
+
+import atexit
+import codecs
+import encodings
+## For AOS, the locale module will need to point to a wrapper around the
+## java.util.Locale class.
+## See https://code.patternsinthevoid.net/?p=android-locale-hack.git
+import locale
+import os
+import psutil
+import subprocess
+import sys
+import threading
+
+from . import _parsers
+from . import _util
+
+from ._parsers import _check_preferences
+from ._parsers import _sanitise_list
+from ._util import log
+
+
+class GPGMeta(type):
+ """Metaclass for changing the :meth:GPG.__init__ initialiser.
+
+ Detects running gpg-agent processes and the presence of a pinentry
+ program, and disables pinentry so that python-gnupg can write the
+ passphrase to the controlled GnuPG process without killing the agent.
+
+ :attr _agent_proc: If a :program:`gpg-agent` process is currently running
+ for the effective userid, then **_agent_proc** will be
+ set to a ``psutil.Process`` for that process.
+ """
+
+ def __new__(cls, name, bases, attrs):
+ """Construct the initialiser for GPG"""
+ log.debug("Metaclass __new__ constructor called for %r" % cls)
+ if cls._find_agent():
+ ## call the normal GPG.__init__() initialiser:
+ attrs['init'] = cls.__init__
+ attrs['_remove_agent'] = True
+ return super(GPGMeta, cls).__new__(cls, name, bases, attrs)
+
+ @classmethod
+ def _find_agent(cls):
+ """Discover if a gpg-agent process for the current euid is running.
+
+ If there is a matching gpg-agent process, set a :class:`psutil.Process`
+ instance containing the gpg-agent process' information to
+ ``cls._agent_proc``.
+
+ :returns: True if there exists a gpg-agent process running under the
+ same effective user ID as that of this program. Otherwise,
+ returns None.
+ """
+ identity = psutil.Process(os.getpid()).uids
+ for proc in psutil.process_iter():
+ if (proc.name == "gpg-agent") and proc.is_running:
+ log.debug("Found gpg-agent process with pid %d" % proc.pid)
+ if proc.uids == identity:
+ log.debug(
+ "Effective UIDs of this process and gpg-agent match")
+ setattr(cls, '_agent_proc', proc)
+ return True
+
+
+class GPGBase(object):
+ """Base class for storing properties and controlling process initialisation.
+
+ :const _result_map: A *dict* containing classes from
+ :mod:`~gnupg._parsers`, used for parsing results
+ obtained from GnuPG commands.
+ :const _decode_errors: How to handle encoding errors.
+ """
+ __metaclass__ = GPGMeta
+ _decode_errors = 'strict'
+ _result_map = { 'crypt': _parsers.Crypt,
+ 'delete': _parsers.DeleteResult,
+ 'generate': _parsers.GenKey,
+ 'import': _parsers.ImportResult,
+ 'list': _parsers.ListKeys,
+ 'sign': _parsers.Sign,
+ 'verify': _parsers.Verify,
+ 'packets': _parsers.ListPackets }
+
+ def __init__(self, binary=None, home=None, keyring=None, secring=None,
+ use_agent=False, default_preference_list=None,
+ verbose=False, options=None):
+ """Create a ``GPGBase``.
+
+ This class is used to set up properties for controlling the behaviour
+ of configuring various options for GnuPG, such as setting GnuPG's
+ **homedir** , and the paths to its **binary** and **keyring** .
+
+ :const binary: (:obj:`str`) The full path to the GnuPG binary.
+
+ :ivar homedir: (:class:`~gnupg._util.InheritableProperty`) The full
+ path to the current setting for the GnuPG
+ ``--homedir``.
+
+ :ivar _generated_keys: (:class:`~gnupg._util.InheritableProperty`)
+ Controls setting the directory for storing any
+ keys which are generated with
+ :meth:`~gnupg.GPG.gen_key`.
+
+ :ivar str keyring: The filename in **homedir** to use as the keyring
+ file for public keys.
+ :ivar str secring: The filename in **homedir** to use as the keyring
+ file for secret keys.
+ """
+ self.binary = _util._find_binary(binary)
+ self.homedir = home if home else _util._conf
+ pub = _parsers._fix_unsafe(keyring) if keyring else 'pubring.gpg'
+ sec = _parsers._fix_unsafe(secring) if secring else 'secring.gpg'
+ self.keyring = os.path.join(self._homedir, pub)
+ self.secring = os.path.join(self._homedir, sec)
+ self.options = _parsers._sanitise(options) if options else None
+
+ if default_preference_list:
+ self._prefs = _check_preferences(default_preference_list, 'all')
+ else:
+ self._prefs = 'SHA512 SHA384 SHA256 AES256 CAMELLIA256 TWOFISH'
+ self._prefs += ' AES192 ZLIB ZIP Uncompressed'
+
+ encoding = locale.getpreferredencoding()
+ if encoding is None: # This happens on Jython!
+ encoding = sys.stdin.encoding
+ self._encoding = encoding.lower().replace('-', '_')
+ self._filesystemencoding = encodings.normalize_encoding(
+ sys.getfilesystemencoding().lower())
+
+ self._keyserver = 'hkp://wwwkeys.pgp.net'
+ self.__generated_keys = os.path.join(self.homedir, 'generated-keys')
+
+ try:
+ assert self.binary, "Could not find binary %s" % binary
+ assert isinstance(verbose, (bool, str, int)), \
+ "'verbose' must be boolean, string, or 0 <= n <= 9"
+ assert isinstance(use_agent, bool), "'use_agent' must be boolean"
+ if self.options is not None:
+ assert isinstance(self.options, str), "options not string"
+ except (AssertionError, AttributeError) as ae:
+ log.error("GPGBase.__init__(): %s" % str(ae))
+ raise RuntimeError(str(ae))
+ else:
+ if verbose is True:
+ # The caller wants logging, but we need a valid --debug-level
+ # for gpg. Default to "basic", and warn about the ambiguity.
+ # (garrettr)
+ verbose = "basic"
+ log.warning('GPG(verbose=True) is ambiguous, defaulting to "basic" logging')
+ self.verbose = verbose
+ self.use_agent = use_agent
+
+ if hasattr(self, '_agent_proc') \
+ and getattr(self, '_remove_agent', None) is True:
+ if hasattr(self, '__remove_path__'):
+ self.__remove_path__('pinentry')
+
+ def __remove_path__(self, prog=None, at_exit=True):
+ """Remove the directories containing a program from the system's
+ ``$PATH``. If ``GPGBase.binary`` is in a directory being removed, it
+ is linked to :file:'./gpg' in the current directory.
+
+ :param str prog: The program to remove from ``$PATH``.
+ :param bool at_exit: Add the program back into the ``$PATH`` when the
+ Python interpreter exits, and delete any symlinks
+ to ``GPGBase.binary`` which were created.
+ """
+ #: A list of ``$PATH`` entries which were removed to disable pinentry.
+ self._removed_path_entries = []
+
+ log.debug("Attempting to remove %s from system PATH" % str(prog))
+ if (prog is None) or (not isinstance(prog, str)): return
+
+ try:
+ program = _util._which(prog)[0]
+ except (OSError, IOError, IndexError) as err:
+ log.err(str(err))
+ log.err("Cannot find program '%s', not changing PATH." % prog)
+ return
+
+ ## __remove_path__ cannot be an @classmethod in GPGMeta, because
+ ## the use_agent attribute must be set by the instance.
+ if not self.use_agent:
+ program_base = os.path.dirname(prog)
+ gnupg_base = os.path.dirname(self.binary)
+
+ ## symlink our gpg binary into $PWD if the path we are removing is
+ ## the one which contains our gpg executable:
+ new_gpg_location = os.path.join(os.getcwd(), 'gpg')
+ if gnupg_base == program_base:
+ os.symlink(self.binary, new_gpg_location)
+ self.binary = new_gpg_location
+
+ ## copy the original environment so that we can put it back later:
+ env_copy = os.environ ## this one should not be touched
+ path_copy = os.environ.pop('PATH')
+ log.debug("Created a copy of system PATH: %r" % path_copy)
+ assert not os.environ.has_key('PATH'), "OS env kept $PATH anyway!"
+
+ @staticmethod
+ def remove_program_from_path(path, prog_base):
+ """Remove all directories which contain a program from PATH.
+
+ :param str path: The contents of the system environment's
+ ``$PATH``.
+
+ :param str prog_base: The directory portion of a program's
+ location, without the trailing slash,
+ and without the program name. For
+ example, ``prog_base='/usr/bin'``.
+ """
+ paths = path.split(':')
+ for directory in paths:
+ if directory == prog_base:
+ log.debug("Found directory with target program: %s"
+ % directory)
+ path.remove(directory)
+ self._removed_path_entries.append(directory)
+ log.debug("Deleted all found instance of %s." % directory)
+ log.debug("PATH is now:%s%s" % (os.linesep, path))
+ new_path = ':'.join([p for p in path])
+ return new_path
+
+ @staticmethod
+ def update_path(environment, path):
+ """Add paths to the string at ``os.environ['PATH']``.
+
+ :param str environment: The environment mapping to update.
+ :param list path: A list of strings to update the PATH with.
+ """
+ log.debug("Updating system path...")
+ os.environ = environment
+ new_path = ':'.join([p for p in path])
+ old = ''
+ if 'PATH' in os.environ:
+ new_path = ':'.join([os.environ['PATH'], new_path])
+ os.environ.update({'PATH': new_path})
+ log.debug("System $PATH: %s" % os.environ['PATH'])
+
+ modified_path = remove_program_from_path(path_copy, program_base)
+ update_path(env_copy, modified_path)
+
+ ## register an _exithandler with the python interpreter:
+ atexit.register(update_path, env_copy, path_copy)
+
+ def remove_symlinked_binary(symlink):
+ if os.path.islink(symlink):
+ os.unlink(symlink)
+ log.debug("Removed binary symlink '%s'" % symlink)
+ atexit.register(remove_symlinked_binary, new_gpg_location)
+
+ @property
+ def default_preference_list(self):
+ """Get the default preference list."""
+ return self._prefs
+
+ @default_preference_list.setter
+ def default_preference_list(self, prefs):
+ """Set the default preference list.
+
+ :param str prefs: A string containing the default preferences for
+ ciphers, digests, and compression algorithms.
+ """
+ prefs = _check_preferences(prefs)
+ if prefs is not None:
+ self._prefs = prefs
+
+ @default_preference_list.deleter
+ def default_preference_list(self):
+ """Reset the default preference list to its original state.
+
+ Note that "original state" does not mean the default preference
+ list for whichever version of GnuPG is being used. It means the
+ default preference list defined by :attr:`GPGBase._prefs`.
+
+ Using BZIP2 is avoided due to not interacting well with some versions
+ of GnuPG>=2.0.0.
+ """
+ self._prefs = 'SHA512 SHA384 SHA256 AES256 CAMELLIA256 TWOFISH ZLIB ZIP'
+
+ @property
+ def keyserver(self):
+ """Get the current keyserver setting."""
+ return self._keyserver
+
+ @keyserver.setter
+ def keyserver(self, location):
+ """Set the default keyserver to use for sending and receiving keys.
+
+ The ``location`` is sent to :func:`_parsers._check_keyserver` when
+ option are parsed in :meth:`gnupg.GPG._make_options`.
+
+ :param str location: A string containing the default keyserver. This
+ should contain the desired keyserver protocol
+ which is supported by the keyserver, for example,
+ ``'hkps://keys.mayfirst.org'``. The default
+ keyserver is ``'hkp://wwwkeys.pgp.net'``.
+ """
+ self._keyserver = location
+
+ @keyserver.deleter
+ def keyserver(self):
+ """Reset the keyserver to the default setting."""
+ self._keyserver = 'hkp://wwwkeys.pgp.net'
+
+ def _homedir_getter(self):
+ """Get the directory currently being used as GnuPG's homedir.
+
+ If unspecified, use :file:`~/.config/python-gnupg/`
+
+ :rtype: str
+ :returns: The absolute path to the current GnuPG homedir.
+ """
+ return self._homedir
+
+ def _homedir_setter(self, directory):
+ """Set the directory to use as GnuPG's homedir.
+
+ If unspecified, use $HOME/.config/python-gnupg. If specified, ensure
+ that the ``directory`` does not contain various shell escape
+ characters. If ``directory`` is not found, it will be automatically
+ created. Lastly, the ``direcory`` will be checked that the EUID has
+ read and write permissions for it.
+
+ :param str directory: A relative or absolute path to the directory to
+ use for storing/accessing GnuPG's files, including
+ keyrings and the trustdb.
+ :raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
+ directory to use.
+ """
+ if not directory:
+ log.debug("GPGBase._homedir_setter(): Using default homedir: '%s'"
+ % _util._conf)
+ directory = _util._conf
+
+ hd = _parsers._fix_unsafe(directory)
+ log.debug("GPGBase._homedir_setter(): got directory '%s'" % hd)
+
+ if hd:
+ log.debug("GPGBase._homedir_setter(): Check existence of '%s'" % hd)
+ _util._create_if_necessary(hd)
+
+ try:
+ log.debug("GPGBase._homedir_setter(): checking permissions")
+ assert _util._has_readwrite(hd), \
+ "Homedir '%s' needs read/write permissions" % hd
+ except AssertionError as ae:
+ msg = ("Unable to set '%s' as GnuPG homedir" % directory)
+ log.debug("GPGBase.homedir.setter(): %s" % msg)
+ log.debug(str(ae))
+ raise RuntimeError(str(ae))
+ else:
+ log.info("Setting homedir to '%s'" % hd)
+ self._homedir = hd
+
+ homedir = _util.InheritableProperty(_homedir_getter, _homedir_setter)
+
+ def _generated_keys_getter(self):
+ """Get the ``homedir`` subdirectory for storing generated keys.
+
+ :rtype: str
+ :returns: The absolute path to the current GnuPG homedir.
+ """
+ return self.__generated_keys
+
+ def _generated_keys_setter(self, directory):
+ """Set the directory for storing generated keys.
+
+ If unspecified, use
+ :meth:`~gnupg._meta.GPGBase.homedir`/generated-keys. If specified,
+ ensure that the ``directory`` does not contain various shell escape
+ characters. If ``directory`` isn't found, it will be automatically
+ created. Lastly, the ``directory`` will be checked to ensure that the
+ current EUID has read and write permissions for it.
+
+ :param str directory: A relative or absolute path to the directory to
+ use for storing/accessing GnuPG's files, including keyrings and
+ the trustdb.
+ :raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable
+ directory to use.
+ """
+ if not directory:
+ directory = os.path.join(self.homedir, 'generated-keys')
+ log.debug("GPGBase._generated_keys_setter(): Using '%s'"
+ % directory)
+
+ hd = _parsers._fix_unsafe(directory)
+ log.debug("GPGBase._generated_keys_setter(): got directory '%s'" % hd)
+
+ if hd:
+ log.debug("GPGBase._generated_keys_setter(): Check exists '%s'"
+ % hd)
+ _util._create_if_necessary(hd)
+
+ try:
+ log.debug("GPGBase._generated_keys_setter(): check permissions")
+ assert _util._has_readwrite(hd), \
+ "Keys dir '%s' needs read/write permissions" % hd
+ except AssertionError as ae:
+ msg = ("Unable to set '%s' as generated keys dir" % directory)
+ log.debug("GPGBase._generated_keys_setter(): %s" % msg)
+ log.debug(str(ae))
+ raise RuntimeError(str(ae))
+ else:
+ log.info("Setting homedir to '%s'" % hd)
+ self.__generated_keys = hd
+
+ _generated_keys = _util.InheritableProperty(_generated_keys_getter,
+ _generated_keys_setter)
+
+ def _make_args(self, args, passphrase=False):
+ """Make a list of command line elements for GPG.
+
+ The value of ``args`` will be appended only if it passes the checks in
+ :func:`gnupg._parsers._sanitise`. The ``passphrase`` argument needs to
+ be True if a passphrase will be sent to GnuPG, else False.
+
+ :param list args: A list of strings of options and flags to pass to
+ ``GPG.binary``. This is input safe, meaning that
+ these values go through strict checks (see
+ ``parsers._sanitise_list``) before being passed to to
+ the input file descriptor for the GnuPG process.
+ Each string should be given exactly as it would be on
+ the commandline interface to GnuPG,
+ e.g. ["--cipher-algo AES256", "--default-key
+ A3ADB67A2CDB8B35"].
+
+ :param bool passphrase: If True, the passphrase will be sent to the
+ stdin file descriptor for the attached GnuPG
+ process.
+ """
+ ## see TODO file, tag :io:makeargs:
+ cmd = [self.binary,
+ '--no-options --no-emit-version --no-tty --status-fd 2']
+
+ if self.homedir: cmd.append('--homedir "%s"' % self.homedir)
+
+ if self.keyring:
+ cmd.append('--no-default-keyring --keyring %s' % self.keyring)
+ if self.secring:
+ cmd.append('--secret-keyring %s' % self.secring)
+
+ if passphrase: cmd.append('--batch --passphrase-fd 0')
+
+ if self.use_agent: cmd.append('--use-agent')
+ else: cmd.append('--no-use-agent')
+
+ if self.options:
+ [cmd.append(opt) for opt in iter(_sanitise_list(self.options))]
+ if args:
+ [cmd.append(arg) for arg in iter(_sanitise_list(args))]
+
+ if self.verbose:
+ cmd.append('--debug-all')
+ if ((isinstance(self.verbose, str) and
+ self.verbose in ['basic', 'advanced', 'expert', 'guru'])
+ or (isinstance(self.verbose, int) and (1<=self.verbose<=9))):
+ cmd.append('--debug-level %s' % self.verbose)
+
+ return cmd
+
+ def _open_subprocess(self, args=None, passphrase=False):
+ """Open a pipe to a GPG subprocess and return the file objects for
+ communicating with it.
+
+ :param list args: A list of strings of options and flags to pass to
+ ``GPG.binary``. This is input safe, meaning that
+ these values go through strict checks (see
+ ``parsers._sanitise_list``) before being passed to to
+ the input file descriptor for the GnuPG process.
+ Each string should be given exactly as it would be on
+ the commandline interface to GnuPG,
+ e.g. ["--cipher-algo AES256", "--default-key
+ A3ADB67A2CDB8B35"].
+
+ :param bool passphrase: If True, the passphrase will be sent to the
+ stdin file descriptor for the attached GnuPG
+ process.
+ """
+ ## see http://docs.python.org/2/library/subprocess.html#converting-an\
+ ## -argument-sequence-to-a-string-on-windows
+ cmd = ' '.join(self._make_args(args, passphrase))
+ log.debug("Sending command to GnuPG process:%s%s" % (os.linesep, cmd))
+ return subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env={'LANGUAGE': 'en'})
+
+ def _read_response(self, stream, result):
+ """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.
+
+ :param stream: A byte-stream, file handle, or a
+ :data:`subprocess.PIPE` for parsing the status codes
+ from the GnuPG process.
+
+ :param result: The result parser class from :mod:`~gnupg._parsers` ―
+ the ``handle_status()`` method of that class will be
+ called in order to parse the output of ``stream``.
+ """
+ lines = []
+ while True:
+ line = stream.readline()
+ if len(line) == 0:
+ break
+ lines.append(line)
+ line = line.rstrip()
+
+ if line.startswith('[GNUPG:]'):
+ line = _util._deprefix(line, '[GNUPG:] ', log.status)
+ keyword, value = _util._separate_keyword(line)
+ result._handle_status(keyword, value)
+ elif line.startswith('gpg:'):
+ line = _util._deprefix(line, 'gpg: ')
+ keyword, value = _util._separate_keyword(line)
+
+ # Log gpg's userland messages at our own levels:
+ if keyword.upper().startswith("WARNING"):
+ log.warn("%s" % value)
+ elif keyword.upper().startswith("FATAL"):
+ log.critical("%s" % value)
+ # Handle the gpg2 error where a missing trustdb.gpg is,
+ # for some stupid reason, considered fatal:
+ if value.find("trustdb.gpg") and value.find("No such file"):
+ result._handle_status('NEED_TRUSTDB', '')
+ else:
+ if self.verbose:
+ log.info("%s" % line)
+ else:
+ log.debug("%s" % line)
+ result.stderr = ''.join(lines)
+
+ def _read_data(self, stream, result):
+ """Incrementally read from ``stream`` and store read data.
+
+ All data gathered from calling ``stream.read()`` will be concatenated
+ and stored as ``result.data``.
+
+ :param stream: An open file-like object to read() from.
+ :param result: An instance of one of the :ref:`result parsing classes
+ <parsers>` from :const:`~gnupg._meta.GPGBase._result_map`.
+ """
+ chunks = []
+ log.debug("Reading data from stream %r..." % stream.__repr__())
+
+ while True:
+ data = stream.read(1024)
+ if len(data) == 0:
+ break
+ chunks.append(data)
+ log.debug("Read %4d bytes" % len(data))
+
+ # Join using b'' or '', as appropriate
+ result.data = type(data)().join(chunks)
+ log.debug("Finishing reading from stream %r..." % stream.__repr__())
+ log.debug("Read %4d bytes total" % len(result.data))
+
+ 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.setDaemon(True)
+ log.debug('stderr reader: %r', rr)
+ rr.start()
+
+ stdout = process.stdout
+ dr = threading.Thread(target=self._read_data, args=(stdout, result))
+ dr.setDaemon(True)
+ log.debug('stdout reader: %r', dr)
+ dr.start()
+
+ dr.join()
+ rr.join()
+ if writer is not None:
+ writer.join()
+ process.wait()
+ if stdin is not None:
+ try:
+ stdin.close()
+ except IOError:
+ pass
+ stderr.close()
+ stdout.close()
+
+ def _handle_io(self, args, file, result, passphrase=False, binary=False):
+ """Handle a call to GPG - pass input data, collect output data."""
+ p = self._open_subprocess(args, passphrase)
+ if not binary:
+ stdin = codecs.getwriter(self._encoding)(p.stdin)
+ else:
+ stdin = p.stdin
+ if passphrase:
+ _util._write_passphrase(stdin, passphrase, self._encoding)
+ writer = _util._threaded_copy_data(file, stdin)
+ self._collect_output(p, result, writer, stdin)
+ return result
+
+ def _recv_keys(self, keyids, keyserver=None):
+ """Import keys from a keyserver.
+
+ :param str keyids: A space-delimited string containing the keyids to
+ request.
+ :param str keyserver: The keyserver to request the ``keyids`` from;
+ defaults to `gnupg.GPG.keyserver`.
+ """
+ if not keyserver:
+ keyserver = self.keyserver
+
+ args = ['--keyserver {0}'.format(keyserver),
+ '--recv-keys {0}'.format(keyids)]
+ log.info('Requesting keys from %s: %s' % (keyserver, keyids))
+
+ result = self._result_map['import'](self)
+ proc = self._open_subprocess(args)
+ self._collect_output(proc, result)
+ log.debug('recv_keys result: %r', result.__dict__)
+ return result
+
+ def _sign_file(self, file, default_key=None, passphrase=None,
+ clearsign=True, detach=False, binary=False,
+ digest_algo='SHA512'):
+ """Create a signature for a file.
+
+ :param file: The file stream (i.e. it's already been open()'d) to sign.
+ :param str default_key: The key to sign with.
+ :param str passphrase: The passphrase to pipe to stdin.
+ :param bool clearsign: If True, create a cleartext signature.
+ :param bool detach: If True, create a detached signature.
+ :param bool binary: If True, do not ascii armour the output.
+ :param str digest_algo: The hash digest to use. Again, to see which
+ hashes your GnuPG is capable of using, do:
+ ``$ gpg --with-colons --list-config
+ digestname``. The default, if unspecified, is
+ ``'SHA512'``.
+ """
+ log.debug("_sign_file():")
+ if binary:
+ log.info("Creating binary signature for file %s" % file)
+ args = ['--sign']
+ else:
+ log.info("Creating ascii-armoured signature for file %s" % file)
+ args = ['--sign --armor']
+
+ if clearsign:
+ args.append("--clearsign")
+ if detach:
+ log.warn("Cannot use both --clearsign and --detach-sign.")
+ log.warn("Using default GPG behaviour: --clearsign only.")
+ elif detach and not clearsign:
+ args.append("--detach-sign")
+
+ if default_key:
+ args.append(str("--default-key %s" % default_key))
+
+ args.append(str("--digest-algo %s" % digest_algo))
+
+ ## 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.
+ result = self._result_map['sign'](self)
+ proc = self._open_subprocess(args, passphrase is not None)
+ try:
+ if passphrase:
+ _util._write_passphrase(proc.stdin, passphrase, self._encoding)
+ writer = _util._threaded_copy_data(file, proc.stdin)
+ except IOError as ioe:
+ log.exception("Error writing message: %s" % str(ioe))
+ writer = None
+ self._collect_output(proc, result, writer, proc.stdin)
+ return result
+
+ def _encrypt(self, data, recipients,
+ default_key=None,
+ passphrase=None,
+ armor=True,
+ encrypt=True,
+ symmetric=False,
+ always_trust=True,
+ output=None,
+ cipher_algo='AES256',
+ digest_algo='SHA512',
+ compress_algo='ZLIB'):
+ """Encrypt the message read from the file-like object **data**.
+
+ :param str data: The file or bytestream to encrypt.
+
+ :param str recipients: The recipients to encrypt to. Recipients must
+ be specified keyID/fingerprint.
+
+ .. warning:: Care should be taken in Python2 to make sure that the
+ given fingerprints for **recipients** are in fact strings
+ and not unicode objects.
+
+ :param str default_key: The keyID/fingerprint of the key to use for
+ signing. If given, **data** will be encrypted
+ *and* signed.
+
+ :param str passphrase: If given, and **default_key** is also given,
+ use this passphrase to unlock the secret
+ portion of the **default_key** to sign the
+ encrypted **data**. Otherwise, if
+ **default_key** is not given, but **symmetric**
+ is ``True``, then use this passphrase as the
+ passphrase for symmetric encryption. Signing
+ and symmetric encryption should *not* be
+ combined when sending the **data** to other
+ recipients, else the passphrase to the secret
+ key would be shared with them.
+
+ :param bool armor: If True, ascii armor the output; otherwise, the
+ output will be in binary format. (Default: True)
+
+ :param bool encrypt: If True, encrypt the **data** using the
+ **recipients** public keys. (Default: True)
+
+ :param bool symmetric: If True, encrypt the **data** to **recipients**
+ using a symmetric key. See the **passphrase**
+ parameter. Symmetric encryption and public key
+ encryption can be used simultaneously, and will
+ result in a ciphertext which is decryptable
+ with either the symmetric **passphrase** or one
+ of the corresponding private keys.
+
+ :param bool always_trust: If True, ignore trust warnings on
+ **recipients** keys. If False, display trust
+ warnings. (default: True)
+
+ :param str output: The output file to write to. If not specified, the
+ encrypted output is returned, and thus should be
+ stored as an object in Python. For example:
+
+
+ >>> import shutil
+ >>> import gnupg
+ >>> if os.path.exists("doctests"):
+ ... shutil.rmtree("doctests")
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key_settings = gpg.gen_key_input(key_type='RSA',
+ ... key_length=1024,
+ ... key_usage='ESCA',
+ ... passphrase='foo')
+ >>> key = gpg.gen_key(key_settings)
+ >>> message = "The crow flies at midnight."
+ >>> encrypted = str(gpg.encrypt(message, key.printprint))
+ >>> assert encrypted != message
+ >>> assert not encrypted.isspace()
+ >>> decrypted = str(gpg.decrypt(encrypted))
+ >>> assert not decrypted.isspace()
+ >>> decrypted
+ 'The crow flies at midnight.'
+
+ :param str cipher_algo: The cipher algorithm to use. To see available
+ algorithms with your version of GnuPG, do:
+ :command:`$ gpg --with-colons --list-config
+ ciphername`. The default **cipher_algo**, if
+ unspecified, is ``'AES256'``.
+
+ :param str digest_algo: The hash digest to use. Again, to see which
+ hashes your GnuPG is capable of using, do:
+ :command:`$ gpg --with-colons --list-config
+ digestname`. The default, if unspecified, is
+ ``'SHA512'``.
+
+ :param str compress_algo: The compression algorithm to use. Can be one
+ of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or
+ ``'Uncompressed'``.
+ """
+ args = []
+
+ if output:
+ if getattr(output, 'fileno', None) is not None:
+ ## avoid overwrite confirmation message
+ if getattr(output, 'name', None) is None:
+ if os.path.exists(output):
+ os.remove(output)
+ args.append('--output %s' % output)
+ else:
+ if os.path.exists(output.name):
+ os.remove(output.name)
+ args.append('--output %s' % output.name)
+
+ if armor: args.append('--armor')
+ if always_trust: args.append('--always-trust')
+ if cipher_algo: args.append('--cipher-algo %s' % cipher_algo)
+ if compress_algo: args.append('--compress-algo %s' % compress_algo)
+
+ if default_key:
+ args.append('--sign')
+ args.append('--default-key %s' % default_key)
+ if digest_algo:
+ args.append('--digest-algo %s' % digest_algo)
+
+ ## both can be used at the same time for an encrypted file which
+ ## is decryptable with a passphrase or secretkey.
+ if symmetric: args.append('--symmetric')
+ if encrypt: args.append('--encrypt')
+
+ if len(recipients) >= 1:
+ log.debug("GPG.encrypt() called for recipients '%s' with type '%s'"
+ % (recipients, type(recipients)))
+
+ if isinstance(recipients, (list, tuple)):
+ for recp in recipients:
+ if not _util._py3k:
+ if isinstance(recp, unicode):
+ try:
+ assert _parsers._is_hex(str(recp))
+ except AssertionError:
+ log.info("Can't accept recipient string: %s"
+ % recp)
+ else:
+ args.append('--recipient %s' % str(recp))
+ continue
+ ## will give unicode in 2.x as '\uXXXX\uXXXX'
+ args.append('--recipient %r' % recp)
+ continue
+ if isinstance(recp, str):
+ args.append('--recipient %s' % recp)
+
+ elif (not _util._py3k) and isinstance(recp, basestring):
+ for recp in recipients.split('\x20'):
+ args.append('--recipient %s' % recp)
+
+ elif _util._py3k and isinstance(recp, str):
+ for recp in recipients.split(' '):
+ args.append('--recipient %s' % recp)
+ ## ...and now that we've proven py3k is better...
+
+ else:
+ log.debug("Don't know what to do with recipients: '%s'"
+ % recipients)
+
+ result = self._result_map['crypt'](self)
+ log.debug("Got data '%s' with type '%s'."
+ % (data, type(data)))
+ self._handle_io(args, data, result,
+ passphrase=passphrase, binary=True)
+ log.debug("\n%s" % result.data)
+ return result
diff --git a/gnupg/_parsers.py b/gnupg/_parsers.py
new file mode 100644
index 0000000..2e1767e
--- /dev/null
+++ b/gnupg/_parsers.py
@@ -0,0 +1,1385 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+'''Classes for parsing GnuPG status messages and sanitising commandline
+options.
+'''
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+try:
+ from collections import OrderedDict
+except ImportError:
+ from ordereddict import OrderedDict
+
+import re
+
+from . import _util
+from ._util import log
+
+
+ESCAPE_PATTERN = re.compile(r'\\x([0-9a-f][0-9a-f])', re.I)
+HEXIDECIMAL = re.compile('([0-9A-Fa-f]{2})+')
+
+
+class ProtectedOption(Exception):
+ """Raised when the option passed to GPG is disallowed."""
+
+class UsageError(Exception):
+ """Raised when incorrect usage of the API occurs.."""
+
+
+def _check_keyserver(location):
+ """Check that a given keyserver is a known protocol and does not contain
+ shell escape characters.
+
+ :param str location: A string containing the default keyserver. This
+ should contain the desired keyserver protocol which
+ is supported by the keyserver, for example, the
+ default is ``'hkp://wwwkeys .pgp.net'``.
+ :rtype: :obj:`str` or :obj:`None`
+ :returns: A string specifying the protocol and keyserver hostname, if the
+ checks passed. If not, returns None.
+ """
+ protocols = ['hkp://', 'hkps://', 'http://', 'https://', 'ldap://',
+ 'mailto:'] ## xxx feels like i´m forgetting one...
+ for proto in protocols:
+ if location.startswith(proto):
+ url = location.replace(proto, str())
+ host, slash, extra = url.partition('/')
+ if extra: log.warn("URI text for %s: '%s'" % (host, extra))
+ log.debug("Got host string for keyserver setting: '%s'" % host)
+
+ host = _fix_unsafe(host)
+ if host:
+ log.debug("Cleaned host string: '%s'" % host)
+ keyserver = proto + host
+ return keyserver
+ return None
+
+def _check_preferences(prefs, pref_type=None):
+ """Check cipher, digest, and compression preference settings.
+
+ MD5 is not allowed. This is `not 1994`__. SHA1 is allowed_ grudgingly_.
+
+ __ http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf
+ .. _allowed: http://eprint.iacr.org/2008/469.pdf
+ .. _grudgingly: https://www.schneier.com/blog/archives/2012/10/when_will_we_se.html
+ """
+ if prefs is None: return
+
+ cipher = frozenset(['AES256', 'AES192', 'AES128',
+ 'CAMELLIA256', 'CAMELLIA192',
+ 'TWOFISH', '3DES'])
+ digest = frozenset(['SHA512', 'SHA384', 'SHA256', 'SHA224', 'RMD160',
+ 'SHA1'])
+ compress = frozenset(['BZIP2', 'ZLIB', 'ZIP', 'Uncompressed'])
+ all = frozenset([cipher, digest, compress])
+
+ if isinstance(prefs, str):
+ prefs = set(prefs.split())
+ elif isinstance(prefs, list):
+ prefs = set(prefs)
+ else:
+ msg = "prefs must be list of strings, or space-separated string"
+ log.error("parsers._check_preferences(): %s" % message)
+ raise TypeError(message)
+
+ if not pref_type:
+ pref_type = 'all'
+
+ allowed = str()
+
+ if pref_type == 'cipher':
+ allowed += ' '.join(prefs.intersection(cipher))
+ if pref_type == 'digest':
+ allowed += ' '.join(prefs.intersection(digest))
+ if pref_type == 'compress':
+ allowed += ' '.join(prefs.intersection(compress))
+ if pref_type == 'all':
+ allowed += ' '.join(prefs.intersection(all))
+
+ return allowed
+
+def _fix_unsafe(shell_input):
+ """Find characters used to escape from a string into a shell, and wrap them in
+ quotes if they exist. Regex pilfered from Python3 :mod:`shlex` module.
+
+ :param str shell_input: The input intended for the GnuPG process.
+ """
+ _unsafe = re.compile(r'[^\w@%+=:,./-]', 256)
+ try:
+ if len(_unsafe.findall(shell_input)) == 0:
+ return shell_input.strip()
+ else:
+ clean = "'" + shell_input.replace("'", "'\"'\"'") + "'"
+ return clean
+ except TypeError:
+ return None
+
+def _hyphenate(input, add_prefix=False):
+ """Change underscores to hyphens so that object attributes can be easily
+ tranlated to GPG option names.
+
+ :param str input: The attribute to hyphenate.
+ :param bool add_prefix: If True, add leading hyphens to the input.
+ :rtype: str
+ :return: The ``input`` with underscores changed to hyphens.
+ """
+ ret = '--' if add_prefix else ''
+ ret += input.replace('_', '-')
+ return ret
+
+def _is_allowed(input):
+ """Check that an option or argument given to GPG is in the set of allowed
+ options, the latter being a strict subset of the set of all options known
+ to GPG.
+
+ :param str input: An input meant to be parsed as an option or flag to the
+ GnuPG process. Should be formatted the same as an option
+ or flag to the commandline gpg, i.e. "--encrypt-files".
+
+ :ivar frozenset gnupg_options: All known GPG options and flags.
+
+ :ivar frozenset allowed: All allowed GPG options and flags, e.g. all GPG
+ options and flags which we are willing to
+ acknowledge and parse. If we want to support a
+ new option, it will need to have its own parsing
+ class and its name will need to be added to this
+ set.
+
+ :raises: :exc:`UsageError` if **input** is not a subset of the hard-coded
+ set of all GnuPG options in :func:`_get_all_gnupg_options`.
+
+ :exc:`ProtectedOption` if **input** is not in the set of allowed
+ options.
+
+ :rtype: str
+ :return: The original **input** parameter, unmodified and unsanitized, if
+ no errors occur.
+ """
+ gnupg_options = _get_all_gnupg_options()
+ allowed = _get_options_group("allowed")
+
+ ## these are the allowed options we will handle so far, all others should
+ ## be dropped. this dance is so that when new options are added later, we
+ ## merely add the to the _allowed list, and the `` _allowed.issubset``
+ ## assertion will check that GPG will recognise them
+ try:
+ ## check that allowed is a subset of all gnupg_options
+ assert allowed.issubset(gnupg_options)
+ except AssertionError:
+ raise UsageError("'allowed' isn't a subset of known options, diff: %s"
+ % allowed.difference(gnupg_options))
+
+ ## if we got a list of args, join them
+ ##
+ ## see TODO file, tag :cleanup:
+ if not isinstance(input, str):
+ input = ' '.join([x for x in input])
+
+ if isinstance(input, str):
+ if input.find('_') > 0:
+ if not input.startswith('--'):
+ hyphenated = _hyphenate(input, add_prefix=True)
+ else:
+ hyphenated = _hyphenate(input)
+ else:
+ hyphenated = input
+ ## xxx we probably want to use itertools.dropwhile here
+ try:
+ assert hyphenated in allowed
+ except AssertionError as ae:
+ dropped = _fix_unsafe(hyphenated)
+ log.warn("_is_allowed(): Dropping option '%s'..." % dropped)
+ raise ProtectedOption("Option '%s' not supported." % dropped)
+ else:
+ return input
+ return None
+
+def _is_hex(string):
+ """Check that a string is hexidecimal, with alphabetic characters
+ capitalized and without whitespace.
+
+ :param str string: The string to check.
+ """
+ matched = HEXIDECIMAL.match(string)
+ if matched is not None and len(matched.group()) >= 2:
+ return True
+ return False
+
+def _is_string(thing):
+ """Python character arrays are a mess.
+
+ If Python2, check if **thing** is an :obj:`unicode` or a :obj:`str`.
+ If Python3, check if **thing** is a :obj:`str`.
+
+ :param thing: The thing to check.
+ :returns: ``True`` if **thing** is a string according to whichever version
+ of Python we're running in.
+ """
+ if _util._py3k: return isinstance(thing, str)
+ else: return isinstance(thing, basestring)
+
+def _sanitise(*args):
+ """Take an arg or the key portion of a kwarg and check that it is in the
+ set of allowed GPG options and flags, and that it has the correct
+ type. Then, attempt to escape any unsafe characters. If an option is not
+ allowed, drop it with a logged warning. Returns a dictionary of all
+ sanitised, allowed options.
+
+ Each new option that we support that is not a boolean, but instead has
+ some additional inputs following it, i.e. "--encrypt-file foo.txt", will
+ need some basic safety checks added here.
+
+ GnuPG has three-hundred and eighteen commandline flags. Also, not all
+ implementations of OpenPGP parse PGP packets and headers in the same way,
+ so there is added potential there for messing with calls to GPG.
+
+ For information on the PGP message format specification, see
+ :rfc:`1991`.
+
+ If you're asking, "Is this *really* necessary?": No, not really -- we could
+ just follow the security precautions recommended by `this xkcd`__.
+
+ __ https://xkcd.com/1181/
+
+ :param str args: (optional) The boolean arguments which will be passed to
+ the GnuPG process.
+ :rtype: str
+ :returns: ``sanitised``
+ """
+
+ ## see TODO file, tag :cleanup:sanitise:
+
+ def _check_option(arg, value):
+ """Check that a single ``arg`` is an allowed option.
+
+ If it is allowed, quote out any escape characters in ``value``, and
+ add the pair to :ivar:`sanitised`. Otherwise, drop them.
+
+ :param str arg: The arguments which will be passed to the GnuPG
+ process, and, optionally their corresponding values.
+ The values are any additional arguments following the
+ GnuPG option or flag. For example, if we wanted to
+ pass ``"--encrypt --recipient isis@leap.se"`` to
+ GnuPG, then ``"--encrypt"`` would be an arg without a
+ value, and ``"--recipient"`` would also be an arg,
+ with a value of ``"isis@leap.se"``.
+
+ :ivar list checked: The sanitised, allowed options and values.
+ :rtype: str
+ :returns: A string of the items in ``checked``, delimited by spaces.
+ """
+ checked = str()
+ none_options = _get_options_group("none_options")
+ hex_options = _get_options_group("hex_options")
+ hex_or_none_options = _get_options_group("hex_or_none_options")
+
+ if not _util._py3k:
+ if not isinstance(arg, list) and isinstance(arg, unicode):
+ arg = str(arg)
+
+ try:
+ flag = _is_allowed(arg)
+ assert flag is not None, "_check_option(): got None for flag"
+ except (AssertionError, ProtectedOption) as error:
+ log.warn("_check_option(): %s" % str(error))
+ else:
+ checked += (flag + ' ')
+
+ if _is_string(value):
+ values = value.split(' ')
+ for v in values:
+ ## these can be handled separately, without _fix_unsafe(),
+ ## because they are only allowed if they pass the regex
+ if (flag in none_options) and (v is None):
+ continue
+
+ if flag in hex_options:
+ if _is_hex(v): checked += (v + " ")
+ else:
+ log.debug("'%s %s' not hex." % (flag, v))
+ if (flag in hex_or_none_options) and (v is None):
+ log.debug("Allowing '%s' for all keys" % flag)
+ continue
+
+ elif flag in ['--keyserver']:
+ host = _check_keyserver(v)
+ if host:
+ log.debug("Setting keyserver: %s" % host)
+ checked += (v + " ")
+ else: log.debug("Dropping keyserver: %s" % v)
+ continue
+
+ ## the rest are strings, filenames, etc, and should be
+ ## shell escaped:
+ val = _fix_unsafe(v)
+ try:
+ assert not val is None
+ assert not val.isspace()
+ assert not v is None
+ assert not v.isspace()
+ except:
+ log.debug("Dropping %s %s" % (flag, v))
+ continue
+
+ if flag in ['--encrypt', '--encrypt-files', '--decrypt',
+ '--decrypt-files', '--import', '--verify']:
+ if ( (_util._is_file(val))
+ or
+ ((flag == '--verify') and (val == '-')) ):
+ checked += (val + " ")
+ else:
+ log.debug("%s not file: %s" % (flag, val))
+
+ elif flag in ['--cipher-algo', '--personal-cipher-prefs',
+ '--personal-cipher-preferences']:
+ legit_algos = _check_preferences(val, 'cipher')
+ if legit_algos: checked += (legit_algos + " ")
+ else: log.debug("'%s' is not cipher" % val)
+
+ elif flag in ['--compress-algo', '--compression-algo',
+ '--personal-compress-prefs',
+ '--personal-compress-preferences']:
+ legit_algos = _check_preferences(val, 'compress')
+ if legit_algos: checked += (legit_algos + " ")
+ else: log.debug("'%s' not compress algo" % val)
+
+ else:
+ checked += (val + " ")
+ log.debug("_check_option(): No checks for %s" % val)
+
+ return checked
+
+ is_flag = lambda x: x.startswith('--')
+
+ def _make_filo(args_string):
+ filo = arg.split(' ')
+ filo.reverse()
+ log.debug("_make_filo(): Converted to reverse list: %s" % filo)
+ return filo
+
+ def _make_groups(filo):
+ groups = {}
+ while len(filo) >= 1:
+ last = filo.pop()
+ if is_flag(last):
+ log.debug("Got arg: %s" % last)
+ if last == '--verify':
+ groups[last] = str(filo.pop())
+ ## accept the read-from-stdin arg:
+ if len(filo) >= 1 and filo[len(filo)-1] == '-':
+ groups[last] += str(' - ') ## gross hack
+ filo.pop()
+ else:
+ groups[last] = str()
+ while len(filo) > 1 and not is_flag(filo[len(filo)-1]):
+ log.debug("Got value: %s" % filo[len(filo)-1])
+ groups[last] += (filo.pop() + " ")
+ else:
+ if len(filo) == 1 and not is_flag(filo[0]):
+ log.debug("Got value: %s" % filo[0])
+ groups[last] += filo.pop()
+ else:
+ log.warn("_make_groups(): Got solitary value: %s" % last)
+ groups["xxx"] = last
+ return groups
+
+ def _check_groups(groups):
+ log.debug("Got groups: %s" % groups)
+ checked_groups = []
+ for a,v in groups.items():
+ v = None if len(v) == 0 else v
+ safe = _check_option(a, v)
+ if safe is not None and not safe.strip() == "":
+ log.debug("Appending option: %s" % safe)
+ checked_groups.append(safe)
+ else:
+ log.warn("Dropped option: '%s %s'" % (a,v))
+ return checked_groups
+
+ if args is not None:
+ option_groups = {}
+ for arg in args:
+ ## if we're given a string with a bunch of options in it split
+ ## them up and deal with them separately
+ if (not _util._py3k and isinstance(arg, basestring)) \
+ or (_util._py3k and isinstance(arg, str)):
+ log.debug("Got arg string: %s" % arg)
+ if arg.find(' ') > 0:
+ filo = _make_filo(arg)
+ option_groups.update(_make_groups(filo))
+ else:
+ option_groups.update({ arg: "" })
+ elif isinstance(arg, list):
+ log.debug("Got arg list: %s" % arg)
+ arg.reverse()
+ option_groups.update(_make_groups(arg))
+ else:
+ log.warn("Got non-str/list arg: '%s', type '%s'"
+ % (arg, type(arg)))
+ checked = _check_groups(option_groups)
+ sanitised = ' '.join(x for x in checked)
+ return sanitised
+ else:
+ log.debug("Got None for args")
+
+def _sanitise_list(arg_list):
+ """A generator for iterating through a list of gpg options and sanitising
+ them.
+
+ :param list arg_list: A list of options and flags for GnuPG.
+ :rtype: generator
+ :returns: A generator whose next() method returns each of the items in
+ ``arg_list`` after calling ``_sanitise()`` with that item as a
+ parameter.
+ """
+ if isinstance(arg_list, list):
+ for arg in arg_list:
+ safe_arg = _sanitise(arg)
+ if safe_arg != "":
+ yield safe_arg
+
+def _get_options_group(group=None):
+ """Get a specific group of options which are allowed."""
+
+ #: These expect a hexidecimal keyid as their argument, and can be parsed
+ #: with :func:`_is_hex`.
+ hex_options = frozenset(['--check-sigs',
+ '--default-key',
+ '--default-recipient',
+ '--delete-keys',
+ '--delete-secret-keys',
+ '--delete-secret-and-public-keys',
+ '--desig-revoke',
+ '--export',
+ '--export-secret-keys',
+ '--export-secret-subkeys',
+ '--fingerprint',
+ '--gen-revoke',
+ '--list-key',
+ '--list-keys',
+ '--list-public-keys',
+ '--list-secret-keys',
+ '--list-sigs',
+ '--recipient',
+ '--recv-keys',
+ '--send-keys',
+ ])
+ #: These options expect value which are left unchecked, though still run
+ #: through :func:`_fix_unsafe`.
+ unchecked_options = frozenset(['--list-options',
+ '--passphrase-fd',
+ '--status-fd',
+ '--verify-options',
+ ])
+ #: These have their own parsers and don't really fit into a group
+ other_options = frozenset(['--debug-level',
+ '--keyserver',
+
+ ])
+ #: These should have a directory for an argument
+ dir_options = frozenset(['--homedir',
+ ])
+ #: These expect a keyring or keyfile as their argument
+ keyring_options = frozenset(['--keyring',
+ '--primary-keyring',
+ '--secret-keyring',
+ '--trustdb-name',
+ ])
+ #: These expect a filename (or the contents of a file as a string) or None
+ #: (meaning that they read from stdin)
+ file_or_none_options = frozenset(['--decrypt',
+ '--decrypt-files',
+ '--encrypt',
+ '--encrypt-files',
+ '--import',
+ '--verify',
+ '--verify-files',
+ ])
+ #: These options expect a string. see :func:`_check_preferences`.
+ pref_options = frozenset(['--digest-algo',
+ '--cipher-algo',
+ '--compress-algo',
+ '--compression-algo',
+ '--cert-digest-algo',
+ '--personal-digest-prefs',
+ '--personal-digest-preferences',
+ '--personal-cipher-prefs',
+ '--personal-cipher-preferences',
+ '--personal-compress-prefs',
+ '--personal-compress-preferences',
+ '--print-md',
+ ])
+ #: These options expect no arguments
+ none_options = frozenset(['--always-trust',
+ '--armor',
+ '--armour',
+ '--batch',
+ '--check-sigs',
+ '--check-trustdb',
+ '--clearsign',
+ '--debug-all',
+ '--default-recipient-self',
+ '--detach-sign',
+ '--export',
+ '--export-ownertrust',
+ '--export-secret-keys',
+ '--export-secret-subkeys',
+ '--fingerprint',
+ '--fixed-list-mode',
+ '--gen-key',
+ '--import-ownertrust',
+ '--list-config',
+ '--list-key',
+ '--list-keys',
+ '--list-packets',
+ '--list-public-keys',
+ '--list-secret-keys',
+ '--list-sigs',
+ '--no-default-keyring',
+ '--no-default-recipient',
+ '--no-emit-version',
+ '--no-options',
+ '--no-tty',
+ '--no-use-agent',
+ '--no-verbose',
+ '--print-mds',
+ '--quiet',
+ '--sign',
+ '--symmetric',
+ '--use-agent',
+ '--verbose',
+ '--version',
+ '--with-colons',
+ '--yes',
+ ])
+ #: These options expect either None or a hex string
+ hex_or_none_options = hex_options.intersection(none_options)
+ allowed = hex_options.union(unchecked_options, other_options, dir_options,
+ keyring_options, file_or_none_options,
+ pref_options, none_options)
+
+ if group and group in locals().keys():
+ return locals()[group]
+
+def _get_all_gnupg_options():
+ """Get all GnuPG options and flags.
+
+ This is hardcoded within a local scope to reduce the chance of a tampered
+ GnuPG binary reporting falsified option sets, i.e. because certain options
+ (namedly the ``--no-options`` option, which prevents the usage of gpg.conf
+ files) are necessary and statically specified in
+ :meth:`gnupg._meta.GPGBase._make_args`, if the inputs into Python are
+ already controlled, and we were to summon the GnuPG binary to ask it for
+ its options, it would be possible to receive a falsified options set
+ missing the ``--no-options`` option in response. This seems unlikely, and
+ the method is stupid and ugly, but at least we'll never have to debug
+ whether or not an option *actually* disappeared in a different GnuPG
+ version, or some funny business is happening.
+
+ These are the options as of GnuPG 1.4.12; the current stable branch of the
+ 2.1.x tree contains a few more -- if you need them you'll have to add them
+ in here.
+
+ :type gnupg_options: frozenset
+ :ivar gnupg_options: All known GPG options and flags.
+ :rtype: frozenset
+ :returns: ``gnupg_options``
+ """
+ three_hundred_eighteen = ("""
+--allow-freeform-uid --multifile
+--allow-multiple-messages --no
+--allow-multisig-verification --no-allow-freeform-uid
+--allow-non-selfsigned-uid --no-allow-multiple-messages
+--allow-secret-key-import --no-allow-non-selfsigned-uid
+--always-trust --no-armor
+--armor --no-armour
+--armour --no-ask-cert-expire
+--ask-cert-expire --no-ask-cert-level
+--ask-cert-level --no-ask-sig-expire
+--ask-sig-expire --no-auto-check-trustdb
+--attribute-fd --no-auto-key-locate
+--attribute-file --no-auto-key-retrieve
+--auto-check-trustdb --no-batch
+--auto-key-locate --no-comments
+--auto-key-retrieve --no-default-keyring
+--batch --no-default-recipient
+--bzip2-compress-level --no-disable-mdc
+--bzip2-decompress-lowmem --no-emit-version
+--card-edit --no-encrypt-to
+--card-status --no-escape-from-lines
+--cert-digest-algo --no-expensive-trust-checks
+--cert-notation --no-expert
+--cert-policy-url --no-force-mdc
+--change-pin --no-force-v3-sigs
+--charset --no-force-v4-certs
+--check-sig --no-for-your-eyes-only
+--check-sigs --no-greeting
+--check-trustdb --no-groups
+--cipher-algo --no-literal
+--clearsign --no-mangle-dos-filenames
+--command-fd --no-mdc-warning
+--command-file --no-options
+--comment --no-permission-warning
+--completes-needed --no-pgp2
+--compress-algo --no-pgp6
+--compression-algo --no-pgp7
+--compress-keys --no-pgp8
+--compress-level --no-random-seed-file
+--compress-sigs --no-require-backsigs
+--ctapi-driver --no-require-cross-certification
+--dearmor --no-require-secmem
+--dearmour --no-rfc2440-text
+--debug --no-secmem-warning
+--debug-all --no-show-notation
+--debug-ccid-driver --no-show-photos
+--debug-level --no-show-policy-url
+--decrypt --no-sig-cache
+--decrypt-files --no-sig-create-check
+--default-cert-check-level --no-sk-comments
+--default-cert-expire --no-strict
+--default-cert-level --notation-data
+--default-comment --not-dash-escaped
+--default-key --no-textmode
+--default-keyserver-url --no-throw-keyid
+--default-preference-list --no-throw-keyids
+--default-recipient --no-tty
+--default-recipient-self --no-use-agent
+--default-sig-expire --no-use-embedded-filename
+--delete-keys --no-utf8-strings
+--delete-secret-and-public-keys --no-verbose
+--delete-secret-keys --no-version
+--desig-revoke --openpgp
+--detach-sign --options
+--digest-algo --output
+--disable-ccid --override-session-key
+--disable-cipher-algo --passphrase
+--disable-dsa2 --passphrase-fd
+--disable-mdc --passphrase-file
+--disable-pubkey-algo --passphrase-repeat
+--display --pcsc-driver
+--display-charset --personal-cipher-preferences
+--dry-run --personal-cipher-prefs
+--dump-options --personal-compress-preferences
+--edit-key --personal-compress-prefs
+--emit-version --personal-digest-preferences
+--enable-dsa2 --personal-digest-prefs
+--enable-progress-filter --pgp2
+--enable-special-filenames --pgp6
+--enarmor --pgp7
+--enarmour --pgp8
+--encrypt --photo-viewer
+--encrypt-files --pipemode
+--encrypt-to --preserve-permissions
+--escape-from-lines --primary-keyring
+--exec-path --print-md
+--exit-on-status-write-error --print-mds
+--expert --quick-random
+--export --quiet
+--export-options --reader-port
+--export-ownertrust --rebuild-keydb-caches
+--export-secret-keys --recipient
+--export-secret-subkeys --recv-keys
+--fast-import --refresh-keys
+--fast-list-mode --remote-user
+--fetch-keys --require-backsigs
+--fingerprint --require-cross-certification
+--fixed-list-mode --require-secmem
+--fix-trustdb --rfc1991
+--force-mdc --rfc2440
+--force-ownertrust --rfc2440-text
+--force-v3-sigs --rfc4880
+--force-v4-certs --run-as-shm-coprocess
+--for-your-eyes-only --s2k-cipher-algo
+--gen-key --s2k-count
+--gen-prime --s2k-digest-algo
+--gen-random --s2k-mode
+--gen-revoke --search-keys
+--gnupg --secret-keyring
+--gpg-agent-info --send-keys
+--gpgconf-list --set-filename
+--gpgconf-test --set-filesize
+--group --set-notation
+--help --set-policy-url
+--hidden-encrypt-to --show-keyring
+--hidden-recipient --show-notation
+--homedir --show-photos
+--honor-http-proxy --show-policy-url
+--ignore-crc-error --show-session-key
+--ignore-mdc-error --sig-keyserver-url
+--ignore-time-conflict --sign
+--ignore-valid-from --sign-key
+--import --sig-notation
+--import-options --sign-with
+--import-ownertrust --sig-policy-url
+--interactive --simple-sk-checksum
+--keyid-format --sk-comments
+--keyring --skip-verify
+--keyserver --status-fd
+--keyserver-options --status-file
+--lc-ctype --store
+--lc-messages --strict
+--limit-card-insert-tries --symmetric
+--list-config --temp-directory
+--list-key --textmode
+--list-keys --throw-keyid
+--list-only --throw-keyids
+--list-options --trustdb-name
+--list-ownertrust --trusted-key
+--list-packets --trust-model
+--list-public-keys --try-all-secrets
+--list-secret-keys --ttyname
+--list-sig --ttytype
+--list-sigs --ungroup
+--list-trustdb --update-trustdb
+--load-extension --use-agent
+--local-user --use-embedded-filename
+--lock-multiple --user
+--lock-never --utf8-strings
+--lock-once --verbose
+--logger-fd --verify
+--logger-file --verify-files
+--lsign-key --verify-options
+--mangle-dos-filenames --version
+--marginals-needed --warranty
+--max-cert-depth --with-colons
+--max-output --with-fingerprint
+--merge-only --with-key-data
+--min-cert-level --yes
+""").split()
+
+ # These are extra options which only exist for GnuPG>=2.0.0
+ three_hundred_eighteen.append('--export-ownertrust')
+ three_hundred_eighteen.append('--import-ownertrust')
+
+ gnupg_options = frozenset(three_hundred_eighteen)
+ return gnupg_options
+
+def nodata(status_code):
+ """Translate NODATA status codes from GnuPG to messages."""
+ lookup = {
+ '1': 'No armored data.',
+ '2': 'Expected a packet but did not find one.',
+ '3': 'Invalid packet found, this may indicate a non OpenPGP message.',
+ '4': 'Signature expected but not found.' }
+ for key, value in lookup.items():
+ if str(status_code) == key:
+ return value
+
+def progress(status_code):
+ """Translate PROGRESS status codes from GnuPG to messages."""
+ lookup = {
+ 'pk_dsa': 'DSA key generation',
+ 'pk_elg': 'Elgamal key generation',
+ 'primegen': 'Prime generation',
+ 'need_entropy': 'Waiting for new entropy in the RNG',
+ 'tick': 'Generic tick without any special meaning - still working.',
+ 'starting_agent': 'A gpg-agent was started.',
+ 'learncard': 'gpg-agent or gpgsm is learning the smartcard data.',
+ 'card_busy': 'A smartcard is still working.' }
+ for key, value in lookup.items():
+ if str(status_code) == key:
+ return value
+
+
+class GenKey(object):
+ """Handle status messages for key generation.
+
+ Calling the ``__str__()`` method of this class will return the generated
+ key's fingerprint, or a status string explaining the results.
+ """
+ def __init__(self, gpg):
+ self._gpg = gpg
+ ## this should get changed to something more useful, like 'key_type'
+ #: 'P':= primary, 'S':= subkey, 'B':= both
+ self.type = None
+ self.fingerprint = None
+ self.status = None
+ self.subkey_created = False
+ self.primary_created = False
+ #: This will store the key's public keyring filename, if
+ #: :meth:`~gnupg.GPG.gen_key_input` was called with
+ #: ``separate_keyring=True``.
+ self.keyring = None
+ #: This will store the key's secret keyring filename, if :
+ #: :meth:`~gnupg.GPG.gen_key_input` was called with
+ #: ``separate_keyring=True``.
+ self.secring = None
+
+ def __nonzero__(self):
+ if self.fingerprint: return True
+ return False
+ __bool__ = __nonzero__
+
+ def __str__(self):
+ if self.fingerprint:
+ return self.fingerprint
+ else:
+ if self.status is not None:
+ return self.status
+ else:
+ return False
+
+ def _handle_status(self, key, value):
+ """Parse a status code from the attached GnuPG process.
+
+ :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+ """
+ if key in ("GOOD_PASSPHRASE"):
+ pass
+ elif key == "KEY_NOT_CREATED":
+ self.status = 'key not created'
+ elif key == "KEY_CREATED":
+ (self.type, self.fingerprint) = value.split()
+ self.status = 'key created'
+ elif key == "NODATA":
+ self.status = nodata(value)
+ elif key == "PROGRESS":
+ self.status = progress(value.split(' ', 1)[0])
+ else:
+ raise ValueError("Unknown status message: %r" % key)
+
+ if self.type in ('B', 'P'):
+ self.primary_created = True
+ if self.type in ('B', 'S'):
+ self.subkey_created = True
+
+class DeleteResult(object):
+ """Handle status messages for --delete-keys and --delete-secret-keys"""
+ def __init__(self, gpg):
+ self._gpg = gpg
+ self.status = 'ok'
+
+ def __str__(self):
+ return self.status
+
+ problem_reason = { '1': 'No such key',
+ '2': 'Must delete secret key first',
+ '3': 'Ambigious specification', }
+
+ def _handle_status(self, key, value):
+ """Parse a status code from the attached GnuPG process.
+
+ :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+ """
+ if key == "DELETE_PROBLEM":
+ self.status = self.problem_reason.get(value, "Unknown error: %r"
+ % value)
+ else:
+ raise ValueError("Unknown status message: %r" % key)
+
+class Sign(object):
+ """Parse GnuPG status messages for signing operations.
+
+ :param gpg: An instance of :class:`gnupg.GPG`.
+ """
+
+ #: The type of signature created.
+ sig_type = None
+ #: The algorithm used to create the signature.
+ sig_algo = None
+ #: The hash algorithm used to create the signature.
+ sig_hash_also = None
+ #: The fingerprint of the signing keyid.
+ fingerprint = None
+ #: The timestamp on the signature.
+ timestamp = None
+ #: xxx fill me in
+ what = None
+
+ def __init__(self, gpg):
+ self._gpg = gpg
+
+ def __nonzero__(self):
+ """Override the determination for truthfulness evaluation.
+
+ :rtype: bool
+ :returns: True if we have a valid signature, False otherwise.
+ """
+ return self.fingerprint is not None
+ __bool__ = __nonzero__
+
+ def __str__(self):
+ return self.data.decode(self._gpg._encoding, self._gpg._decode_errors)
+
+ def _handle_status(self, key, value):
+ """Parse a status code from the attached GnuPG process.
+
+ :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+ """
+ if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
+ "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL",
+ "INV_SGNR", "SIGEXPIRED"):
+ pass
+ elif key == "SIG_CREATED":
+ (self.sig_type, self.sig_algo, self.sig_hash_algo,
+ self.what, self.timestamp, self.fingerprint) = value.split()
+ elif key == "KEYEXPIRED":
+ self.status = "skipped signing key, key expired"
+ if (value is not None) and (len(value) > 0):
+ self.status += " on {}".format(str(value))
+ elif key == "KEYREVOKED":
+ self.status = "skipped signing key, key revoked"
+ if (value is not None) and (len(value) > 0):
+ self.status += " on {}".format(str(value))
+ elif key == "NODATA":
+ self.status = nodata(value)
+ else:
+ raise ValueError("Unknown status message: %r" % key)
+
+class ListKeys(list):
+ """Handle status messages for --list-keys.
+
+ Handles pub and uid (relating the latter to the former). Don't care about
+ the following attributes/status messages (from doc/DETAILS):
+
+ | crt = X.509 certificate
+ | crs = X.509 certificate and private key available
+ | ssb = secret subkey (secondary key)
+ | 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
+ """
+
+ def __init__(self, gpg):
+ super(ListKeys, self).__init__()
+ self._gpg = gpg
+ self.curkey = None
+ self.fingerprints = []
+ self.uids = []
+
+ def key(self, args):
+ vars = ("""
+ type trust length algo keyid date expires dummy ownertrust uid
+ """).split()
+ self.curkey = {}
+ for i in range(len(vars)):
+ self.curkey[vars[i]] = args[i]
+ self.curkey['uids'] = []
+ if self.curkey['uid']:
+ self.curkey['uids'].append(self.curkey['uid'])
+ del self.curkey['uid']
+ self.curkey['subkeys'] = []
+ self.append(self.curkey)
+
+ pub = sec = key
+
+ def fpr(self, args):
+ self.curkey['fingerprint'] = args[9]
+ self.fingerprints.append(args[9])
+
+ def uid(self, args):
+ uid = args[9]
+ uid = ESCAPE_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), uid)
+ self.curkey['uids'].append(uid)
+ self.uids.append(uid)
+
+ def sub(self, args):
+ subkey = [args[4], args[11]]
+ self.curkey['subkeys'].append(subkey)
+
+ def _handle_status(self, key, value):
+ pass
+
+
+class ImportResult(object):
+ """Parse GnuPG status messages for key import operations.
+
+ :type gpg: :class:`gnupg.GPG`
+ :param gpg: An instance of :class:`gnupg.GPG`.
+ """
+ _ok_reason = {'0': 'Not actually changed',
+ '1': 'Entirely new key',
+ '2': 'New user IDs',
+ '4': 'New signatures',
+ '8': 'New subkeys',
+ '16': 'Contains private key',
+ '17': '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', }
+
+ _fields = '''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()
+ _counts = OrderedDict(
+ zip(_fields, [int(0) for x in range(len(_fields))]) )
+
+ #: A list of strings containing the fingerprints of the GnuPG keyIDs
+ #: imported.
+ fingerprints = list()
+
+ #: A list containing dictionaries with information gathered on keys
+ #: imported.
+ results = list()
+
+ def __init__(self, gpg):
+ self._gpg = gpg
+ self.counts = self._counts
+
+ def __nonzero__(self):
+ """Override the determination for truthfulness evaluation.
+
+ :rtype: bool
+ :returns: True if we have immport some keys, False otherwise.
+ """
+ if self.counts.not_imported > 0: return False
+ if len(self.fingerprints) == 0: return False
+ return True
+ __bool__ = __nonzero__
+
+ def _handle_status(self, key, value):
+ """Parse a status code from the attached GnuPG process.
+
+ :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+ """
+ if key == "IMPORTED":
+ # this duplicates info we already see in import_ok & import_problem
+ pass
+ elif key == "NODATA":
+ self.results.append({'fingerprint': None,
+ 'status': 'No valid data found'})
+ elif key == "IMPORT_OK":
+ reason, fingerprint = value.split()
+ reasons = []
+ for code, text in self._ok_reason.items():
+ if int(reason) == int(code):
+ reasons.append(text)
+ reasontext = '\n'.join(reasons) + "\n"
+ self.results.append({'fingerprint': fingerprint,
+ 'status': reasontext})
+ self.fingerprints.append(fingerprint)
+ elif key == "IMPORT_PROBLEM":
+ try:
+ reason, fingerprint = value.split()
+ except:
+ reason = value
+ fingerprint = '<unknown>'
+ self.results.append({'fingerprint': fingerprint,
+ 'status': self._problem_reason[reason]})
+ elif key == "IMPORT_RES":
+ import_res = value.split()
+ for x in self.counts.keys():
+ self.counts[x] = int(import_res.pop(0))
+ elif key == "KEYEXPIRED":
+ res = {'fingerprint': None,
+ 'status': 'Key expired'}
+ self.results.append(res)
+ ## Accoring to docs/DETAILS L859, SIGEXPIRED is obsolete:
+ ## "Removed on 2011-02-04. This is deprecated in favor of KEYEXPIRED."
+ elif key == "SIGEXPIRED":
+ res = {'fingerprint': None,
+ 'status': 'Signature expired'}
+ self.results.append(res)
+ else:
+ raise ValueError("Unknown status message: %r" % key)
+
+ def summary(self):
+ l = []
+ l.append('%d imported' % self.counts['imported'])
+ if self.counts['not_imported']:
+ l.append('%d not imported' % self.counts['not_imported'])
+ return ', '.join(l)
+
+
+class Verify(object):
+ """Parser for status messages from GnuPG for certifications and signature
+ verifications.
+
+ People often mix these up, or think that they are the same thing. While it
+ is true that certifications and signatures *are* the same cryptographic
+ operation -- and also true that both are the same as the decryption
+ operation -- a distinction is made for important reasons.
+
+ A certification:
+ * is made on a key,
+ * can help to validate or invalidate the key owner's identity,
+ * can assign trust levels to the key (or to uids and/or subkeys that
+ the key contains),
+ * and can be used in absense of in-person fingerprint checking to try
+ to build a path (through keys whose fingerprints have been checked)
+ to the key, so that the identity of the key's owner can be more
+ reliable without having to actually physically meet in person.
+
+ A signature:
+ * is created for a file or other piece of data,
+ * can help to prove that the data hasn't been altered,
+ * and can help to prove that the data was sent by the person(s) in
+ possession of the private key that created the signature, and for
+ parsing portions of status messages from decryption operations.
+
+ There are probably other things unique to each that have been
+ scatterbrainedly omitted due to the programmer sitting still and staring
+ at GnuPG debugging logs for too long without snacks, but that is the gist
+ of it.
+ """
+
+ TRUST_UNDEFINED = 0
+ TRUST_NEVER = 1
+ TRUST_MARGINAL = 2
+ TRUST_FULLY = 3
+ TRUST_ULTIMATE = 4
+
+ TRUST_LEVELS = {"TRUST_UNDEFINED" : TRUST_UNDEFINED,
+ "TRUST_NEVER" : TRUST_NEVER,
+ "TRUST_MARGINAL" : TRUST_MARGINAL,
+ "TRUST_FULLY" : TRUST_FULLY,
+ "TRUST_ULTIMATE" : TRUST_ULTIMATE,}
+
+ def __init__(self, gpg):
+ """Create a parser for verification and certification commands.
+
+ :param gpg: An instance of :class:`gnupg.GPG`.
+ """
+ self._gpg = gpg
+ #: True if the signature is valid, False otherwise.
+ self.valid = False
+ #: A string describing the status of the signature verification.
+ #: Can be one of ``signature bad``, ``signature good``,
+ #: ``signature valid``, ``signature error``, ``decryption failed``,
+ #: ``no public key``, ``key exp``, or ``key rev``.
+ self.status = None
+ #: The fingerprint of the signing keyid.
+ self.fingerprint = None
+ #: The fingerprint of the corresponding public key, which may be
+ #: different if the signature was created with a subkey.
+ self.pubkey_fingerprint = None
+ #: The keyid of the signing key.
+ self.key_id = None
+ #: The id of the signature itself.
+ self.signature_id = None
+ #: The creation date of the signing key.
+ self.creation_date = None
+ #: The timestamp of the purported signature, if we are unable to parse
+ #: and/or validate it.
+ self.timestamp = None
+ #: The timestamp for when the valid signature was created.
+ self.sig_timestamp = None
+ #: The userid of the signing key which was used to create the
+ #: signature.
+ self.username = None
+ #: When the signing key is due to expire.
+ self.expire_timestamp = None
+ #: An integer 0-4 describing the trust level of the signature.
+ self.trust_level = None
+ #: The string corresponding to the ``trust_level`` number.
+ self.trust_text = None
+
+ def __nonzero__(self):
+ """Override the determination for truthfulness evaluation.
+
+ :rtype: bool
+ :returns: True if we have a valid signature, False otherwise.
+ """
+ return self.valid
+ __bool__ = __nonzero__
+
+ def _handle_status(self, key, value):
+ """Parse a status code from the attached GnuPG process.
+
+ :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+ """
+ if key in self.TRUST_LEVELS:
+ self.trust_text = key
+ self.trust_level = self.TRUST_LEVELS[key]
+ elif key in ("RSA_OR_IDEA", "NODATA", "IMPORT_RES", "PLAINTEXT",
+ "PLAINTEXT_LENGTH", "POLICY_URL", "DECRYPTION_INFO",
+ "DECRYPTION_OKAY", "INV_SGNR"):
+ pass
+ elif key == "BADSIG":
+ self.valid = False
+ self.status = 'signature bad'
+ self.key_id, self.username = value.split(None, 1)
+ elif key == "GOODSIG":
+ self.valid = True
+ self.status = 'signature good'
+ self.key_id, self.username = value.split(None, 1)
+ elif key == "VALIDSIG":
+ (self.fingerprint,
+ self.creation_date,
+ self.sig_timestamp,
+ self.expire_timestamp) = value.split()[:4]
+ # may be different if signature is made with a subkey
+ self.pubkey_fingerprint = value.split()[-1]
+ self.status = 'signature valid'
+ elif key == "SIG_ID":
+ (self.signature_id,
+ self.creation_date, self.timestamp) = value.split()
+ elif key == "ERRSIG":
+ self.valid = False
+ (self.key_id,
+ algo, hash_algo,
+ cls,
+ self.timestamp) = value.split()[:5]
+ self.status = 'signature error'
+ elif key == "DECRYPTION_FAILED":
+ self.valid = False
+ self.key_id = value
+ self.status = 'decryption failed'
+ elif key == "NO_PUBKEY":
+ self.valid = False
+ self.key_id = value
+ self.status = 'no public key'
+ elif key in ("KEYEXPIRED", "SIGEXPIRED"):
+ # these are useless in verify, since they are spit out for any
+ # pub/subkeys on the key, not just the one doing the signing.
+ # if we want to check for signatures with expired key,
+ # the relevant flag is EXPKEYSIG.
+ pass
+ elif key in ("EXPKEYSIG", "REVKEYSIG"):
+ # signed with expired or revoked key
+ self.valid = False
+ self.key_id = value.split()[0]
+ self.status = (('%s %s') % (key[:3], key[3:])).lower()
+ else:
+ raise ValueError("Unknown status message: %r" % key)
+
+
+class Crypt(Verify):
+ """Parser for internal status messages from GnuPG for ``--encrypt``,
+ ``--decrypt``, and ``--decrypt-files``.
+ """
+ def __init__(self, gpg):
+ Verify.__init__(self, gpg)
+ self._gpg = gpg
+ #: A string containing the encrypted or decrypted data.
+ self.data = ''
+ #: True if the decryption/encryption process turned out okay.
+ self.ok = False
+ #: A string describing the current processing status, or error, if one
+ #: has occurred.
+ self.status = None
+ self.data_format = None
+ self.data_timestamp = None
+ self.data_filename = None
+
+ def __nonzero__(self):
+ if self.ok: return True
+ return False
+ __bool__ = __nonzero__
+
+ def __str__(self):
+ """The str() method for a :class:`Crypt` object will automatically return the
+ decoded data string, which stores the encryped or decrypted data.
+
+ In other words, these two statements are equivalent:
+
+ >>> assert decrypted.data == str(decrypted)
+
+ """
+ return self.data.decode(self._gpg._encoding, self._gpg._decode_errors)
+
+ def _handle_status(self, key, value):
+ """Parse a status code from the attached GnuPG process.
+
+ :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+ """
+ if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
+ "BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA",
+ "CARDCTRL"):
+ # in the case of ERROR, this is because a more specific error
+ # message will have come first
+ pass
+ elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE",
+ "MISSING_PASSPHRASE", "DECRYPTION_FAILED",
+ "KEY_NOT_CREATED"):
+ self.status = key.replace("_", " ").lower()
+ elif key == "NEED_TRUSTDB":
+ self._gpg._create_trustdb()
+ elif key == "NEED_PASSPHRASE_SYM":
+ self.status = 'need symmetric passphrase'
+ elif key == "BEGIN_DECRYPTION":
+ 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":
+ self.status = 'invalid recipient'
+ elif key == "KEYEXPIRED":
+ self.status = 'key expired'
+ elif key == "KEYREVOKED":
+ self.status = 'key revoked'
+ elif key == "SIG_CREATED":
+ self.status = 'sig created'
+ elif key == "SIGEXPIRED":
+ self.status = 'sig expired'
+ elif key == "PLAINTEXT":
+ fmt, dts = value.split(' ', 1)
+ if dts.find(' ') > 0:
+ self.data_timestamp, self.data_filename = dts.split(' ', 1)
+ else:
+ self.data_timestamp = dts
+ ## GnuPG gives us a hex byte for an ascii char corresponding to
+ ## the data format of the resulting plaintext,
+ ## i.e. '62'→'b':= binary data
+ self.data_format = chr(int(str(fmt), 16))
+ else:
+ super(Crypt, self)._handle_status(key, value)
+
+class ListPackets(object):
+ """Handle status messages for --list-packets."""
+
+ def __init__(self, gpg):
+ self._gpg = gpg
+ #: A string describing the current processing status, or error, if one
+ #: has occurred.
+ self.status = None
+ #: True if the passphrase to a public/private keypair is required.
+ self.need_passphrase = None
+ #: True if a passphrase for a symmetric key is required.
+ self.need_passphrase_sym = None
+ #: The keyid and uid which this data is encrypted to.
+ self.userid_hint = None
+
+ def _handle_status(self, key, value):
+ """Parse a status code from the attached GnuPG process.
+
+ :raises: :exc:`~exceptions.ValueError` if the status message is unknown.
+ """
+ if key == 'NODATA':
+ self.status = nodata(value)
+ elif key == 'ENC_TO':
+ # This will only capture keys in our keyring. In the future we
+ # may want to include multiple unknown keys in this list.
+ self.key, _, _ = value.split()
+ elif key == 'NEED_PASSPHRASE':
+ self.need_passphrase = True
+ elif key == 'NEED_PASSPHRASE_SYM':
+ self.need_passphrase_sym = True
+ elif key == 'USERID_HINT':
+ self.userid_hint = value.strip().split()
+ elif key in ('NO_SECKEY', 'BEGIN_DECRYPTION', 'DECRYPTION_FAILED',
+ 'END_DECRYPTION'):
+ pass
+ else:
+ raise ValueError("Unknown status message: %r" % key)
diff --git a/gnupg/_trust.py b/gnupg/_trust.py
new file mode 100644
index 0000000..514ae8c
--- /dev/null
+++ b/gnupg/_trust.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+'''Functions for handling trustdb and trust calculations.
+
+The functions within this module take an instance of :class:`gnupg.GPGBase` or
+a suitable subclass as their first argument.
+'''
+
+from __future__ import absolute_import
+
+import os
+
+from . import _util
+from ._util import log
+
+def _create_trustdb(cls):
+ """Create the trustdb file in our homedir, if it doesn't exist."""
+ trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
+ if not os.path.isfile(trustdb):
+ log.info("GnuPG complained that your trustdb file was missing. %s"
+ % "This is likely due to changing to a new homedir.")
+ log.info("Creating trustdb.gpg file in your GnuPG homedir.")
+ cls.fix_trustdb(trustdb)
+
+def export_ownertrust(cls, trustdb=None):
+ """Export ownertrust to a trustdb file.
+
+ If there is already a file named :file:`trustdb.gpg` in the current GnuPG
+ homedir, it will be renamed to :file:`trustdb.gpg.bak`.
+
+ :param string trustdb: The path to the trustdb.gpg file. If not given,
+ defaults to ``'trustdb.gpg'`` in the current GnuPG
+ homedir.
+ """
+ if trustdb is None:
+ trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
+
+ try:
+ os.rename(trustdb, trustdb + '.bak')
+ except (OSError, IOError) as err:
+ log.debug(str(err))
+
+ export_proc = cls._open_subprocess('--export-ownertrust')
+ tdb = open(trustdb, 'wb')
+ _util._threaded_copy_data(export_proc.stdout, tdb)
+
+def import_ownertrust(self, trustdb=None):
+ """Import ownertrust from a trustdb file.
+
+ :param str trustdb: The path to the trustdb.gpg file. If not given,
+ defaults to :file:`trustdb.gpg` in the current GnuPG
+ homedir.
+ """
+ if trustdb is None:
+ trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
+
+ import_proc = cls._open_subprocess('--import-ownertrust')
+ tdb = open(trustdb, 'rb')
+ _util._threaded_copy_data(tdb, import_proc.stdin)
+
+def fix_trustdb(cls, trustdb=None):
+ """Attempt to repair a broken trustdb.gpg file.
+
+ GnuPG>=2.0.x has this magical-seeming flag: `--fix-trustdb`. You'd think
+ it would fix the the trustdb. Hah! It doesn't. Here's what it does
+ instead::
+
+ (gpg)~/code/python-gnupg $ gpg2 --fix-trustdb
+ gpg: You may try to re-create the trustdb using the commands:
+ gpg: cd ~/.gnupg
+ gpg: gpg2 --export-ownertrust > otrust.tmp
+ gpg: rm trustdb.gpg
+ gpg: gpg2 --import-ownertrust < otrust.tmp
+ gpg: If that does not work, please consult the manual
+
+ Brilliant piece of software engineering right there.
+
+ :param str trustdb: The path to the trustdb.gpg file. If not given,
+ defaults to :file:`trustdb.gpg` in the current GnuPG
+ homedir.
+ """
+ if trustdb is None:
+ trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
+ export_proc = cls._open_subprocess('--export-ownertrust')
+ import_proc = cls._open_subprocess('--import-ownertrust')
+ _util._threaded_copy_data(export_proc.stdout, import_proc.stdin)
diff --git a/gnupg/_util.py b/gnupg/_util.py
new file mode 100644
index 0000000..e1e14ab
--- /dev/null
+++ b/gnupg/_util.py
@@ -0,0 +1,617 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+'''Extra utilities for python-gnupg.'''
+
+from __future__ import absolute_import
+from datetime import datetime
+from socket import gethostname
+from time import localtime
+from time import mktime
+
+import codecs
+import encodings
+import os
+import psutil
+import threading
+import random
+import re
+import string
+import sys
+
+try:
+ from io import StringIO
+ from io import BytesIO
+except ImportError:
+ from cStringIO import StringIO
+
+from . import _logger
+
+
+try:
+ unicode
+ _py3k = False
+ try:
+ isinstance(__name__, basestring)
+ except NameError:
+ msg = "Sorry, python-gnupg requires a Python version with proper"
+ msg += " unicode support. Please upgrade to Python>=2.6."
+ raise SystemExit(msg)
+except NameError:
+ _py3k = True
+
+
+## Directory shortcuts:
+## we don't want to use this one because it writes to the install dir:
+#_here = getabsfile(currentframe()).rsplit(os.path.sep, 1)[0]
+_here = os.path.join(os.getcwd(), 'gnupg') ## current dir
+_test = os.path.join(os.path.join(_here, 'test'), 'tmp') ## ./tests/tmp
+_user = os.environ.get('HOME') ## $HOME
+_ugpg = os.path.join(_user, '.gnupg') ## $HOME/.gnupg
+_conf = os.path.join(os.path.join(_user, '.config'), 'python-gnupg')
+ ## $HOME/.config/python-gnupg
+
+## Logger is disabled by default
+log = _logger.create_logger(0)
+
+
+def find_encodings(enc=None, system=False):
+ """Find functions for encoding translations for a specific codec.
+
+ :param str enc: The codec to find translation functions for. It will be
+ normalized by converting to lowercase, excluding
+ everything which is not ascii, and hyphens will be
+ converted to underscores.
+
+ :param bool system: If True, find encodings based on the system's stdin
+ encoding, otherwise assume utf-8.
+
+ :raises: :exc:LookupError if the normalized codec, ``enc``, cannot be
+ found in Python's encoding translation map.
+ """
+ if not enc:
+ enc = 'utf-8'
+
+ if system:
+ if getattr(sys.stdin, 'encoding', None) is None:
+ enc = sys.stdin.encoding
+ log.debug("Obtained encoding from stdin: %s" % enc)
+ else:
+ enc = 'ascii'
+
+ ## have to have lowercase to work, see
+ ## http://docs.python.org/dev/library/codecs.html#standard-encodings
+ enc = enc.lower()
+ codec_alias = encodings.normalize_encoding(enc)
+
+ codecs.register(encodings.search_function)
+ coder = codecs.lookup(codec_alias)
+
+ return coder
+
+def author_info(name, contact=None, public_key=None):
+ """Easy object-oriented representation of contributor info.
+
+ :param str name: The contributor´s name.
+ :param str contact: The contributor´s email address or contact
+ information, if given.
+ :param str public_key: The contributor´s public keyid, if given.
+ """
+ return Storage(name=name, contact=contact, public_key=public_key)
+
+def _copy_data(instream, outstream):
+ """Copy data from one stream to another.
+
+ :type instream: :class:`io.BytesIO` or :class:`io.StringIO` or file
+ :param instream: A byte stream or open file to read from.
+ :param file outstream: The file descriptor of a tmpfile to write to.
+ """
+ sent = 0
+
+ coder = find_encodings()
+
+ while True:
+ if ((_py3k and isinstance(instream, str)) or
+ (not _py3k and isinstance(instream, basestring))):
+ data = instream[:1024]
+ instream = instream[1024:]
+ else:
+ data = instream.read(1024)
+ if len(data) == 0:
+ break
+ sent += len(data)
+ log.debug("Sending chunk %d bytes:\n%s"
+ % (sent, data))
+ try:
+ outstream.write(data)
+ except UnicodeError:
+ try:
+ outstream.write(coder.encode(data))
+ except IOError:
+ log.exception("Error sending data: Broken pipe")
+ break
+ except IOError as ioe:
+ # Can get 'broken pipe' errors even when all data was sent
+ if 'Broken pipe' in str(ioe):
+ log.error('Error sending data: Broken pipe')
+ else:
+ log.exception(ioe)
+ break
+ try:
+ outstream.close()
+ except IOError as ioe:
+ log.error("Unable to close outstream %s:\r\t%s" % (outstream, ioe))
+ else:
+ log.debug("Closed outstream: %d bytes sent." % sent)
+
+def _create_if_necessary(directory):
+ """Create the specified directory, if necessary.
+
+ :param str directory: The directory to use.
+ :rtype: bool
+ :returns: True if no errors occurred and the directory was created or
+ existed beforehand, False otherwise.
+ """
+
+ if not os.path.isabs(directory):
+ log.debug("Got non-absolute path: %s" % directory)
+ directory = os.path.abspath(directory)
+
+ if not os.path.isdir(directory):
+ log.info("Creating directory: %s" % directory)
+ try:
+ os.makedirs(directory, 0x1C0)
+ except OSError as ose:
+ log.error(ose, exc_info=1)
+ return False
+ else:
+ log.debug("Created directory.")
+ return True
+
+def create_uid_email(username=None, hostname=None):
+ """Create an email address suitable for a UID on a GnuPG key.
+
+ :param str username: The username portion of an email address. If None,
+ defaults to the username of the running Python
+ process.
+
+ :param str hostname: The FQDN portion of an email address. If None, the
+ hostname is obtained from gethostname(2).
+
+ :rtype: str
+ :returns: A string formatted as <username>@<hostname>.
+ """
+ if hostname:
+ hostname = hostname.replace(' ', '_')
+ if not username:
+ try: username = os.environ['LOGNAME']
+ except KeyError: username = os.environ['USERNAME']
+
+ if not hostname: hostname = gethostname()
+
+ uid = "%s@%s" % (username.replace(' ', '_'), hostname)
+ else:
+ username = username.replace(' ', '_')
+ if (not hostname) and (username.find('@') == 0):
+ uid = "%s@%s" % (username, gethostname())
+ elif hostname:
+ uid = "%s@%s" % (username, hostname)
+ else:
+ uid = username
+
+ return uid
+
+def _deprefix(line, prefix, callback=None):
+ """Remove the prefix string from the beginning of line, if it exists.
+
+ :param string line: A line, such as one output by GnuPG's status-fd.
+ :param string prefix: A substring to remove from the beginning of
+ ``line``. Case insensitive.
+ :type callback: callable
+ :param callback: Function to call if the prefix is found. The signature to
+ callback will be only one argument, the ``line`` without the ``prefix``, i.e.
+ ``callback(line)``.
+ :rtype: string
+ :returns: If the prefix was found, the ``line`` without the prefix is
+ returned. Otherwise, the original ``line`` is returned.
+ """
+ try:
+ assert line.upper().startswith(u''.join(prefix).upper())
+ except AssertionError:
+ log.debug("Line doesn't start with prefix '%s':\n%s" % (prefix, line))
+ return line
+ else:
+ newline = line[len(prefix):]
+ if callback is not None:
+ try:
+ callback(newline)
+ except Exception as exc:
+ log.exception(exc)
+ return newline
+
+def _find_binary(binary=None):
+ """Find the absolute path to the GnuPG binary.
+
+ Also run checks that the binary is not a symlink, and check that
+ our process real uid has exec permissions.
+
+ :param str binary: The path to the GnuPG binary.
+ :raises: :exc:`~exceptions.RuntimeError` if it appears that GnuPG is not
+ installed.
+ :rtype: str
+ :returns: The absolute path to the GnuPG binary to use, if no exceptions
+ occur.
+ """
+ found = None
+ if binary is not None:
+ if not os.path.isabs(binary):
+ try:
+ found = _which(binary)
+ log.debug("Found potential binary paths: %s"
+ % '\n'.join([path for path in found]))
+ found = found[0]
+ except IndexError as ie:
+ log.info("Could not determine absolute path of binary: '%s'"
+ % binary)
+ elif os.access(binary, os.X_OK):
+ found = binary
+ if found is None:
+ try: found = _which('gpg')[0]
+ except IndexError as ie:
+ log.error("Could not find binary for 'gpg'.")
+ try: found = _which('gpg2')[0]
+ except IndexError as ie:
+ log.error("Could not find binary for 'gpg2'.")
+ if found is None:
+ raise RuntimeError("GnuPG is not installed!")
+
+ try:
+ assert os.path.isabs(found), "Path to gpg binary not absolute"
+ assert not os.path.islink(found), "Path to gpg binary is symlink"
+ assert os.access(found, os.X_OK), "Lacking +x perms for gpg binary"
+ except (AssertionError, AttributeError) as ae:
+ log.error(str(ae))
+ else:
+ return found
+
+def _has_readwrite(path):
+ """
+ Determine if the real uid/gid of the executing user has read and write
+ permissions for a directory or a file.
+
+ :param str path: The path to the directory or file to check permissions
+ for.
+ :rtype: bool
+ :returns: True if real uid/gid has read+write permissions, False otherwise.
+ """
+ return os.access(path, os.R_OK ^ os.W_OK)
+
+def _is_file(filename):
+ """Check that the size of the thing which is supposed to be a filename has
+ size greater than zero, without following symbolic links or using
+ :func:os.path.isfile.
+
+ :param filename: An object to check.
+ :rtype: bool
+ :returns: True if **filename** is file-like, False otherwise.
+ """
+ try:
+ statinfo = os.lstat(filename)
+ log.debug("lstat(%r) with type=%s gave us %r"
+ % (repr(filename), type(filename), repr(statinfo)))
+ if not (statinfo.st_size > 0):
+ raise ValueError("'%s' appears to be an empty file!" % filename)
+ except OSError as oserr:
+ log.error(oserr)
+ if filename == '-':
+ log.debug("Got '-' for filename, assuming sys.stdin...")
+ return True
+ except (ValueError, TypeError, IOError) as err:
+ log.error(err)
+ else:
+ return True
+ return False
+
+def _is_stream(input):
+ """Check that the input is a byte stream.
+
+ :param input: An object provided for reading from or writing to.
+ :rtype: bool
+ :returns: True if :param:input is a stream, False if otherwise.
+ """
+ return isinstance(input, BytesIO) or isinstance(input, StringIO)
+
+def _is_list_or_tuple(instance):
+ """Check that ``instance`` is a list or tuple.
+
+ :param instance: The object to type check.
+ :rtype: bool
+ :returns: True if ``instance`` is a list or tuple, False otherwise.
+ """
+ return isinstance(instance, (list, tuple,))
+
+def _is_gpg1(version):
+ """Returns True if using GnuPG version 1.x.
+
+ :param tuple version: A tuple of three integers indication major, minor,
+ and micro version numbers.
+ """
+ (major, minor, micro) = _match_version_string(version)
+ if major == 1:
+ return True
+ return False
+
+def _is_gpg2(version):
+ """Returns True if using GnuPG version 2.x.
+
+ :param tuple version: A tuple of three integers indication major, minor,
+ and micro version numbers.
+ """
+ (major, minor, micro) = _match_version_string(version)
+ if major == 2:
+ return True
+ return False
+
+def _make_binary_stream(s, encoding):
+ """
+ xxx fill me in
+ """
+ try:
+ if _py3k:
+ if isinstance(s, str):
+ s = s.encode(encoding)
+ else:
+ if type(s) is not str:
+ s = s.encode(encoding)
+ from io import BytesIO
+ rv = BytesIO(s)
+ except ImportError:
+ rv = StringIO(s)
+ return rv
+
+def _make_passphrase(length=None, save=False, file=None):
+ """Create a passphrase and write it to a file that only the user can read.
+
+ This is not very secure, and should not be relied upon for actual key
+ passphrases.
+
+ :param int length: The length in bytes of the string to generate.
+
+ :param file file: The file to save the generated passphrase in. If not
+ given, defaults to 'passphrase-<the real user id>-<seconds since
+ epoch>' in the top-level directory.
+ """
+ if not length:
+ length = 40
+
+ passphrase = _make_random_string(length)
+
+ if save:
+ ruid, euid, suid = psutil.Process(os.getpid()).uids
+ gid = os.getgid()
+ now = mktime(localtime())
+
+ if not file:
+ filename = str('passphrase-%s-%s' % uid, now)
+ file = os.path.join(_repo, filename)
+
+ with open(file, 'a') as fh:
+ fh.write(passphrase)
+ fh.flush()
+ fh.close()
+ os.chmod(file, stat.S_IRUSR | stat.S_IWUSR)
+ os.chown(file, ruid, gid)
+
+ log.warn("Generated passphrase saved to %s" % file)
+ return passphrase
+
+def _make_random_string(length):
+ """Returns a random lowercase, uppercase, alphanumerical string.
+
+ :param int length: The length in bytes of the string to generate.
+ """
+ chars = string.ascii_lowercase + string.ascii_uppercase + string.digits
+ return ''.join(random.choice(chars) for x in range(length))
+
+def _match_version_string(version):
+ """Sort a binary version string into major, minor, and micro integers.
+
+ :param str version: A version string in the form x.x.x
+ """
+ regex = re.compile('(\d)*(\.)*(\d)*(\.)*(\d)*')
+ matched = regex.match(version)
+ g = matched.groups()
+ major, minor, micro = int(g[0]), int(g[2]), int(g[4])
+ return (major, minor, micro)
+
+def _next_year():
+ """Get the date of today plus one year.
+
+ :rtype: str
+ :returns: The date of this day next year, in the format '%Y-%m-%d'.
+ """
+ now = datetime.now().__str__()
+ date = now.split(' ', 1)[0]
+ year, month, day = date.split('-', 2)
+ next_year = str(int(year)+1)
+ return '-'.join((next_year, month, day))
+
+def _now():
+ """Get a timestamp for right now, formatted according to ISO 8601."""
+ return datetime.isoformat(datetime.now())
+
+def _separate_keyword(line):
+ """Split the line, and return (first_word, the_rest)."""
+ try:
+ first, rest = line.split(None, 1)
+ except ValueError:
+ first = line.strip()
+ rest = ''
+ return first, rest
+
+def _threaded_copy_data(instream, outstream):
+ """Copy data from one stream to another in a separate thread.
+
+ Wraps ``_copy_data()`` in a :class:`threading.Thread`.
+
+ :type instream: :class:`io.BytesIO` or :class:`io.StringIO`
+ :param instream: A byte stream to read from.
+ :param file outstream: The file descriptor of a tmpfile to write to.
+ """
+ copy_thread = threading.Thread(target=_copy_data,
+ args=(instream, outstream))
+ copy_thread.setDaemon(True)
+ log.debug('%r, %r, %r', copy_thread, instream, outstream)
+ copy_thread.start()
+ return copy_thread
+
+def _utc_epoch():
+ """Get the seconds since epoch."""
+ return int(mktime(localtime()))
+
+def _which(executable, flags=os.X_OK):
+ """Borrowed from Twisted's :mod:twisted.python.proutils .
+
+ Search PATH for executable files with the given name.
+
+ On newer versions of MS-Windows, the PATHEXT environment variable will be
+ set to the list of file extensions for files considered executable. This
+ will normally include things like ".EXE". This fuction will also find files
+ with the given name ending with any of these extensions.
+
+ On MS-Windows the only flag that has any meaning is os.F_OK. Any other
+ flags will be ignored.
+
+ Note: This function does not help us prevent an attacker who can already
+ manipulate the environment's PATH settings from placing malicious code
+ higher in the PATH. It also does happily follows links.
+
+ :param str name: The name for which to search.
+ :param int flags: Arguments to L{os.access}.
+ :rtype: list
+ :returns: A list of the full paths to files found, in the order in which
+ they were found.
+ """
+ result = []
+ exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
+ path = os.environ.get('PATH', None)
+ if path is None:
+ return []
+ for p in os.environ.get('PATH', '').split(os.pathsep):
+ p = os.path.join(p, executable)
+ if os.access(p, flags):
+ result.append(p)
+ for e in exts:
+ pext = p + e
+ if os.access(pext, flags):
+ result.append(pext)
+ return result
+
+def _write_passphrase(stream, passphrase, encoding):
+ """Write the passphrase from memory to the GnuPG process' stdin.
+
+ :type stream: file, :class:`~io.BytesIO`, or :class:`~io.StringIO`
+ :param stream: The input file descriptor to write the password to.
+ :param str passphrase: The passphrase for the secret key material.
+ :param str encoding: The data encoding expected by GnuPG. Usually, this
+ is ``sys.getfilesystemencoding()``.
+ """
+ passphrase = '%s\n' % passphrase
+ passphrase = passphrase.encode(encoding)
+ stream.write(passphrase)
+ log.debug("Wrote passphrase on stdin.")
+
+
+class InheritableProperty(object):
+ """Based on the emulation of PyProperty_Type() in Objects/descrobject.c"""
+
+ def __init__(self, fget=None, fset=None, fdel=None, doc=None):
+ self.fget = fget
+ self.fset = fset
+ self.fdel = fdel
+ self.__doc__ = doc
+
+ def __get__(self, obj, objtype=None):
+ if obj is None:
+ return self
+ if self.fget is None:
+ raise AttributeError("unreadable attribute")
+ if self.fget.__name__ == '<lambda>' or not self.fget.__name__:
+ return self.fget(obj)
+ else:
+ return getattr(obj, self.fget.__name__)()
+
+ def __set__(self, obj, value):
+ if self.fset is None:
+ raise AttributeError("can't set attribute")
+ if self.fset.__name__ == '<lambda>' or not self.fset.__name__:
+ self.fset(obj, value)
+ else:
+ getattr(obj, self.fset.__name__)(value)
+
+ def __delete__(self, obj):
+ if self.fdel is None:
+ raise AttributeError("can't delete attribute")
+ if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__:
+ self.fdel(obj)
+ else:
+ getattr(obj, self.fdel.__name__)()
+
+
+class Storage(dict):
+ """A dictionary where keys are stored as class attributes.
+
+ For example, ``obj.foo`` can be used in addition to ``obj['foo']``:
+
+ >>> o = Storage(a=1)
+ >>> o.a
+ 1
+ >>> o['a']
+ 1
+ >>> o.a = 2
+ >>> o['a']
+ 2
+ >>> del o.a
+ >>> o.a
+ None
+ """
+ def __getattr__(self, key):
+ try:
+ return self[key]
+ except KeyError as k:
+ return None
+
+ def __setattr__(self, key, value):
+ self[key] = value
+
+ def __delattr__(self, key):
+ try:
+ del self[key]
+ except KeyError as k:
+ raise AttributeError(k.args[0])
+
+ def __repr__(self):
+ return '<Storage ' + dict.__repr__(self) + '>'
+
+ def __getstate__(self):
+ return dict(self)
+
+ def __setstate__(self, value):
+ for (k, v) in value.items():
+ self[k] = v
diff --git a/gnupg/_version.py b/gnupg/_version.py
new file mode 100644
index 0000000..f9859c5
--- /dev/null
+++ b/gnupg/_version.py
@@ -0,0 +1,11 @@
+
+# This file was generated by 'versioneer.py' (0.7+) from
+# revision-control system data, or from the parent directory name of an
+# unpacked source archive. Distribution tarballs contain a pre-generated copy
+# of this file.
+
+version_version = '1.2.6-6-g6fa8b59'
+version_full = '6fa8b59d33ff573a988a8195bd513526feb81af2'
+def get_versions(default={}, verbose=False):
+ return {'version': version_version, 'full': version_full}
+
diff --git a/gnupg/copyleft.py b/gnupg/copyleft.py
new file mode 100644
index 0000000..6e81e1c
--- /dev/null
+++ b/gnupg/copyleft.py
@@ -0,0 +1,749 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+'''copyleft.py
+--------------
+Copyright information for python-gnupg.
+'''
+
+from __future__ import absolute_import
+
+from . import _util
+
+
+authors = { 'lovecruft_isis': _util.author_info(
+ 'Isis Agora Lovecruft', 'isis@leap.se', '0xA3ADB67A2CDB8B35'),
+
+ 'sajip_vinay': _util.author_info(
+ 'Vinay Sajip', 'vinay.sajip@gmail.com', '0xDE6EF0B2'),
+
+ 'traugott_steve': _util.author_info(
+ 'Steve Traugott', 'stevegt@terraluna.org'),
+
+ 'kuchling_am': _util.author_info(
+ 'A.M. Kuchling', 'amk@amk.ca'), }
+
+copyright = """\
+Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+ © 2013 Andrej B.
+ © 2013 LEAP Encryption Access Project
+ © 2008-2012 Vinay Sajip
+ © 2005 Steve Traugott
+ © 2004 A.M. Kuchling
+All rights reserved.
+See included LICENSE or ``print(gnupg.__license__)`` for full license."""
+
+disclaimer = """\
+This file is part of python-gnupg, a Python wrapper around GnuPG.
+%s
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+""" % (copyright,)
+
+txcopyright = """\
+Where stated, parts of this program were taken from Twisted, which is
+licensed as follows:
+
+Twisted, the Framework of Your Internet
+Copyright © 2001-2013 Twisted Matrix Laboratories.
+See LICENSE for details.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""
+
+
+GPLv3_text = """\
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+
+ BEGIN ORIGINAL LICENSE TEXT
+
+Copyright (c) 2008-2012 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.
+
+ END ORIGINAL LICENSE TEXT
+"""
+
+full_text = "%s\n\n%s\n\n%s" % (disclaimer, txcopyright, GPLv3_text)
diff --git a/gnupg/gnupg.py b/gnupg/gnupg.py
new file mode 100644
index 0000000..9aa8232
--- /dev/null
+++ b/gnupg/gnupg.py
@@ -0,0 +1,1067 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+"""gnupg.py
+===========
+A Python interface to GnuPG.
+
+.. moduleauthor:: Isis Lovecruft <isis@patternsinthevoid.net>
+ see also :attr:`gnupg.__authors__`
+.. license:: see :attr:`gnupg.__license__`
+.. info:: https://github.com/isislovecruft/python-gnupg
+"""
+
+from __future__ import absolute_import
+from codecs import open as open
+
+import encodings
+import functools
+import os
+import re
+import textwrap
+
+try:
+ from io import StringIO
+except ImportError:
+ from cStringIO import StringIO
+
+#: see :pep:`328` http://docs.python.org/2.5/whatsnew/pep-328.html
+from . import _parsers
+from . import _util
+from . import _trust
+from ._meta import GPGBase
+from ._parsers import _fix_unsafe
+from ._util import _is_list_or_tuple
+from ._util import _is_stream
+from ._util import _make_binary_stream
+from ._util import log
+
+
+class GPG(GPGBase):
+ """Python interface for handling interactions with GnuPG, including keyfile
+ generation, keyring maintainance, import and export, encryption and
+ decryption, sending to and recieving from keyservers, and signing and
+ verification.
+ """
+
+ #: The number of simultaneous keyids we should list operations like
+ #: '--list-sigs' to:
+ _batch_limit = 25
+
+ def __init__(self, binary=None, homedir=None, verbose=False,
+ use_agent=False, keyring=None, secring=None,
+ options=None):
+ """Initialize a GnuPG process wrapper.
+
+ :param str binary: Name for GnuPG binary executable. If the absolute
+ path is not given, the environment variable
+ ``$PATH`` is searched for the executable and
+ checked that the real uid/gid of the user has
+ sufficient permissions.
+
+ :param str homedir: Full pathname to directory containing the public
+ and private keyrings. Default is whatever GnuPG
+ defaults to.
+
+ :type verbose: :obj:`str` or :obj:`int` or :obj:`bool`
+ :param verbose: String or numeric value to pass to GnuPG's
+ ``--debug-level`` option. See the GnuPG man page for
+ the list of valid options. If False, debug output is
+ not generated by the GnuPG binary. If True, defaults
+ to ``--debug-level basic.``
+
+ :param str keyring: Name of keyring file containing public key data.
+ If unspecified, defaults to :file:`pubring.gpg` in
+ the **homedir** directory.
+
+ :param str secring: Name of alternative secret keyring file to use. If
+ left unspecified, this will default to using
+ :file:`secring.gpg` in the **homedir** directory,
+ and create that file if it does not exist.
+
+ :param list options: A list of additional options to pass to the GnuPG
+ binary.
+
+ :raises: A :exc:`~exceptions.RuntimeError` with explanation message
+ if there is a problem invoking GnuPG.
+
+ Example:
+
+ >>> import gnupg
+ GnuPG logging disabled...
+ >>> gpg = gnupg.GPG(homedir='doctests')
+ >>> gpg.keyring
+ './doctests/pubring.gpg'
+ >>> gpg.secring
+ './doctests/secring.gpg'
+ >>> gpg.use_agent
+ False
+ >>> gpg.binary
+ '/usr/bin/gpg'
+ """
+
+ super(GPG, self).__init__(
+ binary=binary,
+ home=homedir,
+ keyring=keyring,
+ secring=secring,
+ options=options,
+ verbose=verbose,
+ use_agent=use_agent,)
+
+ log.info(textwrap.dedent("""
+ Initialised settings:
+ binary: %s
+ homedir: %s
+ keyring: %s
+ secring: %s
+ default_preference_list: %s
+ keyserver: %s
+ options: %s
+ verbose: %s
+ use_agent: %s
+ """ % (self.binary, self.homedir, self.keyring, self.secring,
+ self.default_preference_list, self.keyserver, self.options,
+ str(self.verbose), str(self.use_agent))))
+
+ self._batch_dir = os.path.join(self.homedir, 'batch-files')
+ self._key_dir = os.path.join(self.homedir, 'generated-keys')
+
+ #: The keyring used in the most recently created batch file
+ self.temp_keyring = None
+ #: The secring used in the most recently created batch file
+ self.temp_secring = None
+ #: The version string of our GnuPG binary
+ self.binary_version = str()
+
+ ## check that everything runs alright, and grab the gpg binary's
+ ## version number while we're at it:
+ proc = self._open_subprocess(["--list-config", "--with-colons"])
+ result = self._result_map['list'](self)
+ self._read_data(proc.stdout, result)
+ if proc.returncode:
+ raise RuntimeError("Error invoking gpg: %s" % result.data)
+
+ version_line = str(result.data).partition(':version:')[2]
+ self.binary_version = version_line.split('\n')[0]
+ log.debug("Using GnuPG version %s" % self.binary_version)
+
+ if _util._is_gpg2:
+ # Make GnuPG>=2.0.0-only methods public:
+ self.fix_trustdb = self._fix_trustdb
+ self.import_ownertrust = self._import_ownertrust
+ self.export_ownertrust = self._export_ownertrust
+
+ # Make sure that the trustdb exists, or else GnuPG will exit with
+ # a fatal error (at least it does with GnuPG>=2.0.0):
+ self._create_trustdb()
+
+ @functools.wraps(_trust._create_trustdb)
+ def _create_trustdb(self):
+ if self.is_gpg2():
+ _trust._create_trustdb(self)
+ else:
+ log.info("Creating the trustdb is only available with GnuPG>=2.x")
+
+ @functools.wraps(_trust.fix_trustdb)
+ def _fix_trustdb(self, trustdb=None):
+ if self.is_gpg2():
+ _trust.fix_trustdb(self)
+ else:
+ log.info("Fixing the trustdb is only available with GnuPG>=2.x")
+
+ @functools.wraps(_trust.import_ownertrust)
+ def _import_ownertrust(self, trustdb=None):
+ if self.is_gpg2():
+ _trust.import_ownertrust(self)
+ else:
+ log.info("Importing ownertrust is only available with GnuPG>=2.x")
+
+ @functools.wraps(_trust.export_ownertrust)
+ def _export_ownertrust(self, trustdb=None):
+ if self.is_gpg2():
+ _trust.export_ownertrust(self)
+ else:
+ log.info("Exporting ownertrust is only available with GnuPG>=2.x")
+
+ def is_gpg1(self):
+ """Returns true if using GnuPG <= 1.x."""
+ return _util._is_gpg1(self.binary_version)
+
+ def is_gpg2(self):
+ """Returns true if using GnuPG >= 2.x."""
+ return _util._is_gpg2(self.binary_version)
+
+ def sign(self, data, **kwargs):
+ """Create a signature for a message string or file.
+
+ Note that this method is not for signing other keys. (In GnuPG's
+ terms, what we all usually call 'keysigning' is actually termed
+ 'certification'...) Even though they are cryptographically the same
+ operation, GnuPG differentiates between them, presumedly because these
+ operations are also the same as the decryption operation. If the
+ ``key_usage``s ``C (certification)``, ``S (sign)``, and ``E
+ (encrypt)``, were all the same key, the key would "wear down" through
+ frequent signing usage -- since signing data is usually done often --
+ meaning that the secret portion of the keypair, also used for
+ decryption in this scenario, would have a statistically higher
+ probability of an adversary obtaining an oracle for it (or for a
+ portion of the rounds in the cipher algorithm, depending on the family
+ of cryptanalytic attack used).
+
+ In simpler terms: this function isn't for signing your friends' keys,
+ it's for something like signing an email.
+
+ :type data: :obj:`str` or :obj:`file`
+ :param data: A string or file stream to sign.
+ :param str default_key: The key to sign with.
+ :param str passphrase: The passphrase to pipe to stdin.
+ :param bool clearsign: If True, create a cleartext signature.
+ :param bool detach: If True, create a detached signature.
+ :param bool binary: If True, do not ascii armour the output.
+ :param str digest_algo: The hash digest to use. Again, to see which
+ hashes your GnuPG is capable of using, do:
+ :command:`$ gpg --with-colons --list-config digestname`.
+ The default, if unspecified, is ``'SHA512'``.
+ """
+ if 'default_key' in kwargs:
+ log.info("Signing message '%r' with keyid: %s"
+ % (data, kwargs['default_key']))
+ else:
+ log.warn("No 'default_key' given! Using first key on secring.")
+
+ if hasattr(data, 'read'):
+ result = self._sign_file(data, **kwargs)
+ elif not _is_stream(data):
+ stream = _make_binary_stream(data, self._encoding)
+ result = self._sign_file(stream, **kwargs)
+ stream.close()
+ else:
+ log.warn("Unable to sign message '%s' with type %s"
+ % (data, type(data)))
+ result = None
+ return result
+
+ def verify(self, data):
+ """Verify the signature on the contents of the string ``data``.
+
+ >>> gpg = GPG(homedir="doctests")
+ >>> input = gpg.gen_key_input(Passphrase='foo')
+ >>> key = gpg.gen_key(input)
+ >>> assert key
+ >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='bar')
+ >>> assert not sig
+ >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='foo')
+ >>> assert sig
+ >>> verify = gpg.verify(sig.data)
+ >>> assert verify
+
+ """
+ f = _make_binary_stream(data, self._encoding)
+ result = self.verify_file(f)
+ f.close()
+ return result
+
+ def verify_file(self, file, sig_file=None):
+ """Verify the signature on the contents of a file or file-like
+ object. Can handle embedded signatures as well as detached
+ signatures. If using detached signatures, the file containing the
+ detached signature should be specified as the ``sig_file``.
+
+ :param file file: A file descriptor object. Its type will be checked
+ with :func:`_util._is_file`.
+
+ :param str sig_file: A file containing the GPG signature data for
+ ``file``. If given, ``file`` is verified via this detached
+ signature.
+ """
+
+ fn = None
+ result = self._result_map['verify'](self)
+
+ if sig_file is None:
+ log.debug("verify_file(): Handling embedded signature")
+ args = ["--verify"]
+ proc = self._open_subprocess(args)
+ writer = _util._threaded_copy_data(file, proc.stdin)
+ self._collect_output(proc, result, writer, stdin=proc.stdin)
+ else:
+ if not _util._is_file(sig_file):
+ log.debug("verify_file(): '%r' is not a file" % sig_file)
+ return result
+ log.debug('verify_file(): Handling detached verification')
+ sig_fh = None
+ data_fh = None
+ try:
+ sig_fh = open(sig_file, 'rb')
+ data_fh = open(file, 'rb')
+ args = ["--verify %s -" % sig_fh.name]
+ proc = self._open_subprocess(args)
+ writer = _util._threaded_copy_data(data_fh, proc.stdin)
+ self._collect_output(proc, result, writer, stdin=proc.stdin)
+ finally:
+ if sig_fh and not sig_fh.closed:
+ sig_fh.close()
+ if data_fh and not data_fh.closed:
+ data_fh.close()
+ return result
+
+ def import_keys(self, key_data):
+ """
+ Import the key_data into our keyring.
+
+ >>> import shutil
+ >>> shutil.rmtree("doctests")
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> inpt = gpg.gen_key_input()
+ >>> key1 = gpg.gen_key(inpt)
+ >>> print1 = str(key1.fingerprint)
+ >>> pubkey1 = gpg.export_keys(print1)
+ >>> seckey1 = gpg.export_keys(print1,secret=True)
+ >>> key2 = gpg.gen_key(inpt)
+ >>> print2 = key2.fingerprint
+ >>> seckeys = gpg.list_keys(secret=True)
+ >>> pubkeys = gpg.list_keys()
+ >>> assert print1 in seckeys.fingerprints
+ >>> assert print1 in pubkeys.fingerprints
+ >>> str(gpg.delete_keys(print1))
+ 'Must delete secret key first'
+ >>> str(gpg.delete_keys(print1,secret=True))
+ 'ok'
+ >>> str(gpg.delete_keys(print1))
+ 'ok'
+ >>> pubkeys = gpg.list_keys()
+ >>> assert not print1 in pubkeys.fingerprints
+ >>> result = gpg.import_keys(pubkey1)
+ >>> pubkeys = gpg.list_keys()
+ >>> seckeys = gpg.list_keys(secret=True)
+ >>> assert not print1 in seckeys.fingerprints
+ >>> assert print1 in pubkeys.fingerprints
+ >>> result = gpg.import_keys(seckey1)
+ >>> assert result
+ >>> seckeys = gpg.list_keys(secret=True)
+ >>> assert print1 in seckeys.fingerprints
+ """
+ ## xxx need way to validate that key_data is actually a valid GPG key
+ ## it might be possible to use --list-packets and parse the output
+
+ result = self._result_map['import'](self)
+ log.info('Importing: %r', key_data[:256])
+ data = _make_binary_stream(key_data, self._encoding)
+ self._handle_io(['--import'], data, result, binary=True)
+ data.close()
+ return result
+
+ def recv_keys(self, *keyids, **kwargs):
+ """Import keys from a keyserver.
+
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key = gpg.recv_keys('hkp://pgp.mit.edu', '3FF0DB166A7476EA')
+ >>> assert key
+
+ :param str keyids: Each ``keyids`` argument should be a string
+ containing a keyid to request.
+ :param str keyserver: The keyserver to request the ``keyids`` from;
+ defaults to `gnupg.GPG.keyserver`.
+ """
+ if keyids:
+ keys = ' '.join([key for key in keyids])
+ return self._recv_keys(keys, **kwargs)
+ else:
+ log.error("No keyids requested for --recv-keys!")
+
+ def delete_keys(self, fingerprints, secret=False, subkeys=False):
+ """Delete a key, or list of keys, from the current keyring.
+
+ The keys must be referred to by their full fingerprints for GnuPG to
+ delete them. If ``secret=True``, the corresponding secret keyring will
+ be deleted from :obj:`.secring`.
+
+ :type fingerprints: :obj:`str` or :obj:`list` or :obj:`tuple`
+ :param fingerprints: A string, or a list/tuple of strings,
+ representing the fingerprint(s) for the key(s)
+ to delete.
+
+ :param bool secret: If True, delete the corresponding secret key(s)
+ also. (default: False)
+
+ :param bool subkeys: If True, delete the secret subkey first, then the
+ public key. (default: False) Same as:
+ :command:`$gpg --delete-secret-and-public-key 0x12345678`.
+ """
+ which = 'keys'
+ if secret:
+ which = 'secret-keys'
+ if subkeys:
+ which = 'secret-and-public-keys'
+
+ if _is_list_or_tuple(fingerprints):
+ fingerprints = ' '.join(fingerprints)
+
+ args = ['--batch']
+ args.append("--delete-{0} {1}".format(which, fingerprints))
+
+ result = self._result_map['delete'](self)
+ p = self._open_subprocess(args)
+ self._collect_output(p, result, stdin=p.stdin)
+ return result
+
+ def export_keys(self, keyids, secret=False, subkeys=False):
+ """Export the indicated ``keyids``.
+
+ :param str keyids: A keyid or fingerprint in any format that GnuPG will
+ accept.
+ :param bool secret: If True, export only the secret key.
+ :param bool subkeys: If True, export the secret subkeys.
+ """
+ which = ''
+ if subkeys:
+ which = '-secret-subkeys'
+ elif secret:
+ which = '-secret-keys'
+
+ if _is_list_or_tuple(keyids):
+ keyids = ' '.join(['%s' % k for k in keyids])
+
+ args = ["--armor"]
+ args.append("--export{0} {1}".format(which, keyids))
+
+ p = self._open_subprocess(args)
+ ## gpg --export produces no status-fd output; stdout will be empty in
+ ## case of failure
+ #stdout, stderr = p.communicate()
+ result = self._result_map['delete'](self) # any result will do
+ self._collect_output(p, result, stdin=p.stdin)
+ log.debug('Exported:%s%r' % (os.linesep, result.data))
+ return result.data.decode(self._encoding, self._decode_errors)
+
+ def list_keys(self, secret=False):
+ """List the keys currently in the keyring.
+
+ The GnuPG option '--show-photos', according to the GnuPG manual, "does
+ not work with --with-colons", but since we can't rely on all versions
+ of GnuPG to explicitly handle this correctly, we should probably
+ include it in the args.
+
+ >>> import shutil
+ >>> shutil.rmtree("doctests")
+ >>> gpg = GPG(homedir="doctests")
+ >>> input = gpg.gen_key_input()
+ >>> result = gpg.gen_key(input)
+ >>> print1 = result.fingerprint
+ >>> result = gpg.gen_key(input)
+ >>> print2 = result.fingerprint
+ >>> pubkeys = gpg.list_keys()
+ >>> assert print1 in pubkeys.fingerprints
+ >>> assert print2 in pubkeys.fingerprints
+ """
+
+ which = 'public-keys'
+ if secret:
+ which = 'secret-keys'
+ args = "--list-%s --fixed-list-mode --fingerprint " % (which,)
+ args += "--with-colons --list-options no-show-photos"
+ args = [args]
+ p = self._open_subprocess(args)
+
+ # there might be some status thingumy here I should handle... (amk)
+ # ...nope, unless you care about expired sigs or keys (stevegt)
+
+ # Get the response information
+ result = self._result_map['list'](self)
+ self._collect_output(p, result, stdin=p.stdin)
+ lines = result.data.decode(self._encoding,
+ self._decode_errors).splitlines()
+ valid_keywords = 'pub uid sec fpr sub'.split()
+ for line in lines:
+ if self.verbose:
+ print(line)
+ log.debug("%r", line.rstrip())
+ if not line:
+ break
+ L = line.strip().split(':')
+ if not L:
+ continue
+ keyword = L[0]
+ if keyword in valid_keywords:
+ getattr(result, keyword)(L)
+ return result
+
+ def list_packets(self, raw_data):
+ """List the packet contents of a file."""
+ args = ["--list-packets"]
+ result = self._result_map['packets'](self)
+ self._handle_io(args, _make_binary_stream(raw_data, self._encoding),
+ result)
+ return result
+
+ def list_sigs(self, *keyids):
+ """Get the signatures for each of the ``keyids``.
+
+ >>> import gnupg
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key_input = gpg.gen_key_input()
+ >>> key = gpg.gen_key(key_input)
+ >>> assert key.fingerprint
+
+ :rtype: dict
+ :returns: A dictionary whose keys are the original keyid parameters,
+ and whose values are lists of signatures.
+ """
+ if len(keyids) > self._batch_limit:
+ raise ValueError(
+ "List signatures is limited to %d keyids simultaneously"
+ % self._batch_limit)
+
+ args = ["--with-colons", "--fixed-list-mode", "--list-sigs"]
+
+ for key in keyids:
+ args.append(key)
+
+ proc = self._open_subprocess(args)
+ result = self._result_map['list'](self)
+ self._collect_output(proc, result, stdin=proc.stdin)
+ return result
+
+ def gen_key(self, input):
+ """Generate a GnuPG key through batch file key generation. See
+ :meth:`GPG.gen_key_input()` for creating the control input.
+
+ >>> import gnupg
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key_input = gpg.gen_key_input()
+ >>> key = gpg.gen_key(key_input)
+ >>> assert key.fingerprint
+
+ :param dict input: A dictionary of parameters and values for the new
+ key.
+ :returns: The result mapping with details of the new key, which is a
+ :class:`GenKey <gnupg._parsers.GenKey>` object.
+ """
+ args = ["--gen-key --batch"]
+ key = self._result_map['generate'](self)
+ f = _make_binary_stream(input, self._encoding)
+ self._handle_io(args, f, key, binary=True)
+ f.close()
+
+ fpr = str(key.fingerprint)
+ if len(fpr) == 20:
+ for d in map(lambda x: os.path.dirname(x),
+ [self.temp_keyring, self.temp_secring]):
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+ if self.temp_keyring:
+ if os.path.isfile(self.temp_keyring):
+ prefix = os.path.join(self.temp_keyring, fpr)
+ try: os.rename(self.temp_keyring, prefix+".pubring")
+ except OSError as ose: log.error(str(ose))
+
+ if self.temp_secring:
+ if os.path.isfile(self.temp_secring):
+ prefix = os.path.join(self.temp_secring, fpr)
+ try: os.rename(self.temp_secring, prefix+".secring")
+ except OSError as ose: log.error(str(ose))
+
+ log.info("Key created. Fingerprint: %s" % fpr)
+ key.keyring = self.temp_keyring
+ key.secring = self.temp_secring
+ self.temp_keyring = None
+ self.temp_secring = None
+
+ return key
+
+ def gen_key_input(self, separate_keyring=False, save_batchfile=False,
+ testing=False, **kwargs):
+ """Generate a batch file for input to :meth:`~gnupg.GPG.gen_key`.
+
+ The GnuPG batch file key generation feature allows unattended key
+ generation by creating a file with special syntax and then providing it
+ to: :command:`gpg --gen-key --batch`. Batch files look like this:
+
+ | Name-Real: Alice
+ | Name-Email: alice@inter.net
+ | Expire-Date: 2014-04-01
+ | Key-Type: RSA
+ | Key-Length: 4096
+ | Key-Usage: cert
+ | Subkey-Type: RSA
+ | Subkey-Length: 4096
+ | Subkey-Usage: encrypt,sign,auth
+ | Passphrase: sekrit
+ | %pubring foo.gpg
+ | %secring sec.gpg
+ | %commit
+
+ which is what this function creates for you. All of the available,
+ non-control parameters are detailed below (control parameters are the
+ ones which begin with a '%'). For example, to generate the batch file
+ example above, use like this:
+
+ >>> import gnupg
+ GnuPG logging disabled...
+ >>> from __future__ import print_function
+ >>> gpg = gnupg.GPG(homedir='doctests')
+ >>> alice = { 'name_real': 'Alice',
+ ... 'name_email': 'alice@inter.net',
+ ... 'expire_date': '2014-04-01',
+ ... 'key_type': 'RSA',
+ ... 'key_length': 4096,
+ ... 'key_usage': '',
+ ... 'subkey_type': 'RSA',
+ ... 'subkey_length': 4096,
+ ... 'subkey_usage': 'encrypt,sign,auth',
+ ... 'passphrase': 'sekrit'}
+ >>> alice_input = gpg.gen_key_input(**alice)
+ >>> print(alice_input)
+ Key-Type: RSA
+ Subkey-Type: RSA
+ Subkey-Usage: encrypt,sign,auth
+ Expire-Date: 2014-04-01
+ Passphrase: sekrit
+ Name-Real: Alice
+ Name-Email: alice@inter.net
+ Key-Length: 4096
+ Subkey-Length: 4096
+ %pubring ./doctests/alice.pubring.gpg
+ %secring ./doctests/alice.secring.gpg
+ %commit
+ <BLANKLINE>
+ >>> alice_key = gpg.gen_key(alice_input)
+ >>> assert alice_key is not None
+ >>> assert alice_key.fingerprint is not None
+ >>> message = "no one else can read my sekrit message"
+ >>> encrypted = gpg.encrypt(message, alice_key.fingerprint)
+ >>> assert isinstance(encrypted.data, str)
+
+ :param bool separate_keyring: Specify for the new key to be written to
+ a separate pubring.gpg and secring.gpg. If True,
+ :meth:`~gnupg.GPG.gen_key` will automatically rename the separate
+ keyring and secring to whatever the fingerprint of the generated
+ key ends up being, suffixed with '.pubring' and '.secring'
+ respectively.
+
+ :param bool save_batchfile: Save a copy of the generated batch file to
+ disk in a file named <name_real>.batch, where <name_real> is the
+ ``name_real`` parameter stripped of punctuation, spaces, and
+ non-ascii characters.
+
+ :param bool testing: Uses a faster, albeit insecure random number
+ generator to create keys. This should only be used for testing
+ purposes, for keys which are going to be created and then soon
+ after destroyed, and never for the generation of actual use keys.
+
+ :param str name_real: The name field of the UID in the generated key.
+ :param str name_comment: The comment in the UID of the generated key.
+
+ :param str name_email: The email in the UID of the generated key.
+ (default: ``$USER`` @ :command:`hostname` ) Remember to use UTF-8
+ encoding for the entirety of the UID. At least one of
+ ``name_real``, ``name_comment``, or ``name_email`` must be
+ provided, or else no user ID is created.
+
+ :param str key_type: One of 'RSA', 'DSA', 'ELG-E', or 'default'.
+ (default: 'RSA', if using GnuPG v1.x, otherwise 'default') Starts
+ a new parameter block by giving the type of the primary key. The
+ algorithm must be capable of signing. This is a required
+ parameter. The algorithm may either be an OpenPGP algorithm number
+ or a string with the algorithm name. The special value ‘default’
+ may be used for algo to create the default key type; in this case
+ a ``key_usage`` should not be given and 'default' must also be
+ used for ``subkey_type``.
+
+ :param int key_length: The requested length of the generated key in
+ bits. (Default: 4096)
+
+ :param str key_grip: hexstring This is an optional hexidecimal string
+ which is used to generate a CSR or certificate for an already
+ existing key. ``key_length`` will be ignored if this parameter
+ is given.
+
+ :param str key_usage: Space or comma delimited string of key
+ usages. Allowed values are ‘encrypt’, ‘sign’, and ‘auth’. This is
+ used to generate the key flags. Please make sure that the
+ algorithm is capable of this usage. Note that OpenPGP requires
+ that all primary keys are capable of certification, so no matter
+ what usage is given here, the ‘cert’ flag will be on. If no
+ ‘Key-Usage’ is specified and the ‘Key-Type’ is not ‘default’, all
+ allowed usages for that particular algorithm are used; if it is
+ not given but ‘default’ is used the usage will be ‘sign’.
+
+ :param str subkey_type: This generates a secondary key
+ (subkey). Currently only one subkey can be handled. See also
+ ``key_type`` above.
+
+ :param int subkey_length: The length of the secondary subkey in bits.
+
+ :param str subkey_usage: Key usage for a subkey; similar to
+ ``key_usage``.
+
+ :type expire_date: :obj:`int` or :obj:`str`
+ :param expire_date: Can be specified as an iso-date or as
+ <int>[d|w|m|y] Set the expiration date for the key (and the
+ subkey). It may either be entered in ISO date format (2000-08-15)
+ or as number of days, weeks, month or years. The special notation
+ "seconds=N" is also allowed to directly give an Epoch
+ value. Without a letter days are assumed. Note that there is no
+ check done on the overflow of the type used by OpenPGP for
+ timestamps. Thus you better make sure that the given value make
+ sense. Although OpenPGP works with time intervals, GnuPG uses an
+ absolute value internally and thus the last year we can represent
+ is 2105.
+
+ :param str creation_date: Set the creation date of the key as stored
+ in the key information and which is also part of the fingerprint
+ calculation. Either a date like "1986-04-26" or a full timestamp
+ like "19860426T042640" may be used. The time is considered to be
+ UTC. If it is not given the current time is used.
+
+ :param str passphrase: The passphrase for the new key. The default is
+ to not use any passphrase. Note that GnuPG>=2.1.x will not allow
+ you to specify a passphrase for batch key generation -- GnuPG will
+ ignore the **passphrase** parameter, stop, and ask the user for
+ the new passphrase. However, we can put the command
+ ``%no-protection`` into the batch key generation file to allow a
+ passwordless key to be created, which can then have its passphrase
+ set later with ``--edit-key``.
+
+ :param str preferences: Set the cipher, hash, and compression
+ preference values for this key. This expects the same type of
+ string as the sub-command ‘setpref’ in the --edit-key menu.
+
+ :param str revoker: Should be given as 'algo:fpr' (case sensitive).
+ Add a designated revoker to the generated key. Algo is the public
+ key algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
+ fpr is the fingerprint of the designated revoker. The optional
+ ‘sensitive’ flag marks the designated revoker as sensitive
+ information. Only v4 keys may be designated revokers.
+
+ :param str keyserver: This is an optional parameter that specifies the
+ preferred keyserver URL for the key.
+
+ :param str handle: This is an optional parameter only used with the
+ status lines ``KEY_CREATED`` and ``KEY_NOT_CREATED``. string may
+ be up to 100 characters and should not contain spaces. It is
+ useful for batch key generation to associate a key parameter block
+ with a status line.
+
+ :rtype: str
+ :returns: A suitable input string for the :meth:`GPG.gen_key` method,
+ the latter of which will create the new keypair.
+
+ See `this GnuPG Manual section`__ for more details.
+
+ __ http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
+ """
+ #: A boolean for determining whether to set subkey_type to 'default'
+ default_type = False
+
+ parms = {}
+
+ ## if using GnuPG version 1.x, then set the default 'Key-Type' to
+ ## 'RSA' because it doesn't understand 'default'
+ parms.setdefault('Key-Type', 'default')
+ if _util._is_gpg1(self.binary_version):
+ parms.setdefault('Key-Type', 'RSA')
+ log.debug("GnuPG v%s detected: setting default key type to %s."
+ % (self.binary_version, parms['Key-Type']))
+ parms.setdefault('Key-Length', 4096)
+ parms.setdefault('Name-Real', "Autogenerated Key")
+ parms.setdefault('Expire-Date', _util._next_year())
+
+ name_email = kwargs.get('name_email')
+ uidemail = _util.create_uid_email(name_email)
+ parms.setdefault('Name-Email', uidemail)
+
+ if testing:
+ ## This specific comment string is required by (some? all?)
+ ## versions of GnuPG to use the insecure PRNG:
+ parms.setdefault('Name-Comment', 'insecure!')
+
+ for key, val in list(kwargs.items()):
+ key = key.replace('_','-').title()
+ ## to set 'cert', 'Key-Usage' must be blank string
+ if not key in ('Key-Usage', 'Subkey-Usage'):
+ if str(val).strip():
+ parms[key] = val
+
+ ## if Key-Type is 'default', make Subkey-Type also be 'default'
+ if parms['Key-Type'] == 'default':
+ default_type = True
+ for field in ('Key-Usage', 'Subkey-Usage',):
+ try: parms.pop(field) ## toss these out, handle manually
+ except KeyError: pass
+
+ ## Key-Type must come first, followed by length
+ out = "Key-Type: %s\n" % parms.pop('Key-Type')
+ out += "Key-Length: %d\n" % parms.pop('Key-Length')
+ if 'Subkey-Type' in parms.keys():
+ out += "Subkey-Type: %s\n" % parms.pop('Subkey-Type')
+ else:
+ if default_type:
+ out += "Subkey-Type: default\n"
+ if 'Subkey-Length' in parms.keys():
+ out += "Subkey-Length: %s\n" % parms.pop('Subkey-Length')
+
+ for key, val in list(parms.items()):
+ out += "%s: %s\n" % (key, val)
+
+ ## There is a problem where, in the batch files, if the '%%pubring'
+ ## and '%%secring' are given as any static string, i.e. 'pubring.gpg',
+ ## that file will always get rewritten without confirmation, killing
+ ## off any keys we had before. So in the case where we wish to
+ ## generate a bunch of keys and then do stuff with them, we should not
+ ## give 'pubring.gpg' as our keyring file, otherwise we will lose any
+ ## keys we had previously.
+
+ if separate_keyring:
+ ring = str(uidemail + '_' + str(_util._utc_epoch()))
+ self.temp_keyring = os.path.join(self.homedir, ring+'.pubring')
+ self.temp_secring = os.path.join(self.homedir, ring+'.secring')
+ out += "%%pubring %s\n" % self.temp_keyring
+ out += "%%secring %s\n" % self.temp_secring
+
+ if testing:
+ ## see TODO file, tag :compatibility:gen_key_input:
+ ##
+ ## Add version detection before the '%no-protection' flag.
+ out += "%no-protection\n"
+ out += "%transient-key\n"
+
+ out += "%commit\n"
+
+ ## if we've been asked to save a copy of the batch file:
+ if save_batchfile and parms['Name-Email'] != uidemail:
+ asc_uid = encodings.normalize_encoding(parms['Name-Email'])
+ filename = _fix_unsafe(asc_uid) + _util._now() + '.batch'
+ save_as = os.path.join(self._batch_dir, filename)
+ readme = os.path.join(self._batch_dir, 'README')
+
+ if not os.path.exists(self._batch_dir):
+ os.makedirs(self._batch_dir)
+
+ ## the following pulls the link to GnuPG's online batchfile
+ ## documentation from this function's docstring and sticks it
+ ## in a README file in the batch directory:
+
+ if getattr(self.gen_key_input, '__doc__', None) is not None:
+ docs = self.gen_key_input.__doc__
+ else:
+ docs = str() ## docstring=None if run with "python -OO"
+ links = '\n'.join(x.strip() for x in docs.splitlines()[-2:])
+ explain = """
+This directory was created by python-gnupg, on {}, and
+it contains saved batch files, which can be given to GnuPG to automatically
+generate keys. Please see
+{}""".format(_util.now(), links) ## sometimes python is awesome.
+
+ with open(readme, 'a+') as fh:
+ [fh.write(line) for line in explain]
+
+ with open(save_as, 'a+') as batch_file:
+ [batch_file.write(line) for line in out]
+
+ return out
+
+ def encrypt(self, data, *recipients, **kwargs):
+ """Encrypt the message contained in ``data`` to ``recipients``.
+
+ :param str data: The file or bytestream to encrypt.
+
+ :param str recipients: The recipients to encrypt to. Recipients must
+ be specified keyID/fingerprint. Care should be taken in Python2.x
+ to make sure that the given fingerprint is in fact a string and
+ not a unicode object.
+
+ :param str default_key: The keyID/fingerprint of the key to use for
+ signing. If given, ``data`` will be encrypted and signed.
+
+ :param str passphrase: If given, and ``default_key`` is also given,
+ use this passphrase to unlock the secret portion of the
+ ``default_key`` to sign the encrypted ``data``. Otherwise, if
+ ``default_key`` is not given, but ``symmetric=True``, then use
+ this passphrase as the passphrase for symmetric
+ encryption. Signing and symmetric encryption should *not* be
+ combined when sending the ``data`` to other recipients, else the
+ passphrase to the secret key would be shared with them.
+
+ :param bool armor: If True, ascii armor the output; otherwise, the
+ output will be in binary format. (Default: True)
+
+ :param bool encrypt: If True, encrypt the ``data`` using the
+ ``recipients`` public keys. (Default: True)
+
+ :param bool symmetric: If True, encrypt the ``data`` to ``recipients``
+ using a symmetric key. See the ``passphrase`` parameter. Symmetric
+ encryption and public key encryption can be used simultaneously,
+ and will result in a ciphertext which is decryptable with either
+ the symmetric ``passphrase`` or one of the corresponding private
+ keys.
+
+ :param bool always_trust: If True, ignore trust warnings on recipient
+ keys. If False, display trust warnings. (default: True)
+
+ :param str output: The output file to write to. If not specified, the
+ encrypted output is returned, and thus should be stored as an
+ object in Python. For example:
+
+ >>> import shutil
+ >>> import gnupg
+ >>> if os.path.exists("doctests"):
+ ... shutil.rmtree("doctests")
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key_settings = gpg.gen_key_input(key_type='RSA',
+ ... key_length=1024,
+ ... key_usage='ESCA',
+ ... passphrase='foo')
+ >>> key = gpg.gen_key(key_settings)
+ >>> message = "The crow flies at midnight."
+ >>> encrypted = str(gpg.encrypt(message, key.printprint))
+ >>> assert encrypted != message
+ >>> assert not encrypted.isspace()
+ >>> decrypted = str(gpg.decrypt(encrypted))
+ >>> assert not decrypted.isspace()
+ >>> decrypted
+ 'The crow flies at midnight.'
+
+
+ :param str cipher_algo: The cipher algorithm to use. To see available
+ algorithms with your version of GnuPG, do:
+ :command:`$ gpg --with-colons --list-config ciphername`.
+ The default ``cipher_algo``, if unspecified, is ``'AES256'``.
+
+ :param str digest_algo: The hash digest to use. Again, to see which
+ hashes your GnuPG is capable of using, do:
+ :command:`$ gpg --with-colons --list-config digestname`.
+ The default, if unspecified, is ``'SHA512'``.
+
+ :param str compress_algo: The compression algorithm to use. Can be one
+ of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
+
+ .. seealso:: :meth:`._encrypt`
+ """
+ stream = _make_binary_stream(data, self._encoding)
+ result = self._encrypt(stream, recipients, **kwargs)
+ stream.close()
+ return result
+
+ def decrypt(self, message, **kwargs):
+ """Decrypt the contents of a string or file-like object ``message``.
+
+ :type message: file or str or :class:`io.BytesIO`
+ :param message: A string or file-like object to decrypt.
+ :param bool always_trust: Instruct GnuPG to ignore trust checks.
+ :param str passphrase: The passphrase for the secret key used for decryption.
+ :param str output: A filename to write the decrypted output to.
+ """
+ stream = _make_binary_stream(message, self._encoding)
+ result = self.decrypt_file(stream, **kwargs)
+ stream.close()
+ return result
+
+ def decrypt_file(self, filename, always_trust=False, passphrase=None,
+ output=None):
+ """Decrypt the contents of a file-like object ``filename`` .
+
+ :param str filename: A file-like object to decrypt.
+ :param bool always_trust: Instruct GnuPG to ignore trust checks.
+ :param str passphrase: The passphrase for the secret key used for decryption.
+ :param str output: A filename to write the decrypted output to.
+ """
+ args = ["--decrypt"]
+ if output: # write the output to a file with the specified name
+ if os.path.exists(output):
+ os.remove(output) # to avoid overwrite confirmation message
+ args.append('--output %s' % output)
+ if always_trust:
+ args.append("--always-trust")
+ result = self._result_map['crypt'](self)
+ self._handle_io(args, filename, result, passphrase, binary=True)
+ log.debug('decrypt result: %r', result.data)
+ return result
+
+class GPGUtilities(object):
+ """Extra tools for working with GnuPG."""
+
+ def __init__(self, gpg):
+ """Initialise extra utility functions."""
+ self._gpg = gpg
+
+ def find_key_by_email(self, email, secret=False):
+ """Find user's key based on their email address.
+
+ :param str email: The email address to search for.
+ :param bool secret: If True, search through secret keyring.
+ """
+ for key in self.list_keys(secret=secret):
+ for uid in key['uids']:
+ if re.search(email, uid):
+ return key
+ raise LookupError("GnuPG public key for email %s not found!" % email)
+
+ def find_key_by_subkey(self, subkey):
+ """Find a key by a fingerprint of one of its subkeys.
+
+ :param str subkey: The fingerprint of the subkey to search for.
+ """
+ for key in self.list_keys():
+ for sub in key['subkeys']:
+ if sub[0] == subkey:
+ return key
+ raise LookupError(
+ "GnuPG public key for subkey %s not found!" % subkey)
+
+ def send_keys(self, keyserver, *keyids):
+ """Send keys to a keyserver."""
+ result = self._result_map['list'](self)
+ log.debug('send_keys: %r', keyids)
+ data = _util._make_binary_stream("", self._encoding)
+ args = ['--keyserver', keyserver, '--send-keys']
+ args.extend(keyids)
+ self._handle_io(args, data, result, binary=True)
+ log.debug('send_keys result: %r', result.__dict__)
+ data.close()
+ return result
+
+ def encrypted_to(self, raw_data):
+ """Return the key to which raw_data is encrypted to."""
+ # TODO: make this support multiple keys.
+ result = self._gpg.list_packets(raw_data)
+ if not result.key:
+ raise LookupError(
+ "Content is not encrypted to a GnuPG key!")
+ try:
+ return self.find_key_by_keyid(result.key)
+ except:
+ return self.find_key_by_subkey(result.key)
+
+ def is_encrypted_sym(self, raw_data):
+ result = self._gpg.list_packets(raw_data)
+ return bool(result.need_passphrase_sym)
+
+ def is_encrypted_asym(self, raw_data):
+ result = self._gpg.list_packets(raw_data)
+ return bool(result.key)
+
+ def is_encrypted(self, raw_data):
+ return self.is_encrypted_asym(raw_data) or self.is_encrypted_sym(raw_data)
+
+if __name__ == "__main__":
+ from .test import test_gnupg
+ test_gnupg.main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0f9e03a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+# sha256: UI5KRMglOjhqD4bZyb1KG0y7L5TojUmhnBUTZTymbEU
+psutil>=1.2.1
+
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..9103812
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,12 @@
+[upload_docs]
+upload-dir = docs/_build/html
+show-response = true
+verbose = true
+
+[upload]
+sign = True
+identity = 0xa3adb67a2cdb8b35
+
+[aliases]
+upload_all = sdist bdist_egg bdist_wheel upload
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..f21bc75
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+#______________________________________________________________________________
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+import setuptools
+import sys
+import os
+import versioneer
+
+
+versioneer.versionfile_source = 'gnupg/_version.py'
+versioneer.versionfile_build = 'gnupg/_version.py'
+versioneer.tag_prefix = ''
+versioneer.parentdir_prefix = 'gnupg-'
+
+__author__ = "Isis Agora Lovecruft"
+__contact__ = 'isis@patternsinthevoid.net'
+__url__ = 'https://github.com/isislovecruft/python-gnupg'
+
+
+def python26():
+ """Returns True if we're running on Python2.6."""
+ if sys.version[:3] == "2.6":
+ return True
+ return False
+
+def get_requirements():
+ """Extract the list of requirements from our requirements.txt.
+
+ :rtype: 2-tuple
+ :returns: Two lists, the first is a list of requirements in the form of
+ pkgname==version. The second is a list of URIs or VCS checkout strings
+ which specify the dependency links for obtaining a copy of the
+ requirement.
+ """
+ requirements_file = os.path.join(os.getcwd(), 'requirements.txt')
+ requirements = []
+ links=[]
+ try:
+ with open(requirements_file) as reqfile:
+ for line in reqfile.readlines():
+ line = line.strip()
+ if line.startswith('#'):
+ continue
+ elif line.startswith(
+ ('https://', 'git://', 'hg://', 'svn://')):
+ links.append(line)
+ else:
+ requirements.append(line)
+
+ except (IOError, OSError) as error:
+ print(error)
+
+ if python26():
+ # Required to make `collections.OrderedDict` available on Python<=2.6
+ requirements.append('ordereddict==1.1#a0ed854ee442051b249bfad0f638bbec')
+
+ return requirements, links
+
+
+requires, deplinks = get_requirements()
+
+
+setuptools.setup(
+ name = "gnupg",
+ description="A Python wrapper for GnuPG",
+ long_description = """\
+This module allows easy access to GnuPG's key management, encryption and \
+signature functionality from Python programs, by interacting with GnuPG \
+through file descriptors. Input arguments are strictly checked and sanitised, \
+and therefore this module should be safe to use in networked applications \
+requiring direct user input. It is intended for use with Python 2.6 or \
+greater.
+""",
+ license="GPLv3+",
+
+ version=versioneer.get_version(),
+ cmdclass=versioneer.get_cmdclass(),
+
+ author=__author__,
+ author_email=__contact__,
+ maintainer=__author__,
+ maintainer_email=__contact__,
+ url=__url__,
+
+ package_dir={'gnupg': 'gnupg'},
+ packages=['gnupg'],
+ package_data={'': ['README', 'LICENSE', 'TODO', 'requirements.txt']},
+ scripts=['versioneer.py'],
+ test_suite='gnupg.test.test_gnupg',
+
+ install_requires=requires,
+ dependency_links=deplinks,
+ extras_require={'docs': ["Sphinx>=1.1",
+ "sphinxcontrib-fulltoc==1.0"]},
+
+ platforms="Linux, BSD, OSX, Windows",
+ download_url="https://github.com/isislovecruft/python-gnupg/archive/master.zip",
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: 3.4",
+ "Topic :: Security :: Cryptography",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Utilities",]
+)
diff --git a/versioneer.py b/versioneer.py
new file mode 100644
index 0000000..57d9941
--- /dev/null
+++ b/versioneer.py
@@ -0,0 +1,656 @@
+#! /usr/bin/python
+
+"""versioneer.py
+
+(like a rocketeer, but for versions)
+
+* https://github.com/warner/python-versioneer
+* Brian Warner
+* License: Public Domain
+* Version: 0.7+
+
+This file helps distutils-based projects manage their version number by just
+creating version-control tags.
+
+For developers who work from a VCS-generated tree (e.g. 'git clone' etc),
+each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a
+version number by asking your version-control tool about the current
+checkout. The version number will be written into a generated _version.py
+file of your choosing, where it can be included by your __init__.py
+
+For users who work from a VCS-generated tarball (e.g. 'git archive'), it will
+compute a version number by looking at the name of the directory created when
+te tarball is unpacked. This conventionally includes both the name of the
+project and a version number.
+
+For users who work from a tarball built by 'setup.py sdist', it will get a
+version number from a previously-generated _version.py file.
+
+As a result, loading code directly from the source tree will not result in a
+real version. If you want real versions from VCS trees (where you frequently
+update from the upstream repository, or do new development), you will need to
+do a 'setup.py version' after each update, and load code from the build/
+directory.
+
+You need to provide this code with a few configuration values:
+
+ versionfile_source:
+ A project-relative pathname into which the generated version strings
+ should be written. This is usually a _version.py next to your project's
+ main __init__.py file. If your project uses src/myproject/__init__.py,
+ this should be 'src/myproject/_version.py'. This file should be checked
+ in to your VCS as usual: the copy created below by 'setup.py
+ update_files' will include code that parses expanded VCS keywords in
+ generated tarballs. The 'build' and 'sdist' commands will replace it with
+ a copy that has just the calculated version string.
+
+ versionfile_build:
+ Like versionfile_source, but relative to the build directory instead of
+ the source directory. These will differ when your setup.py uses
+ 'package_dir='. If you have package_dir={'myproject': 'src/myproject'},
+ then you will probably have versionfile_build='myproject/_version.py' and
+ versionfile_source='src/myproject/_version.py'.
+
+ tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all
+ VCS tags. If your tags look like 'myproject-1.2.0', then you
+ should use tag_prefix='myproject-'. If you use unprefixed tags
+ like '1.2.0', this should be an empty string.
+
+ parentdir_prefix: a string, frequently the same as tag_prefix, which
+ appears at the start of all unpacked tarball filenames. If
+ your tarball unpacks into 'myproject-1.2.0', this should
+ be 'myproject-'.
+
+To use it:
+
+ 1: include this file in the top level of your project
+ 2: make the following changes to the top of your setup.py:
+ import versioneer
+ versioneer.versionfile_source = 'src/myproject/_version.py'
+ versioneer.versionfile_build = 'myproject/_version.py'
+ versioneer.tag_prefix = '' # tags are like 1.2.0
+ versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0'
+ 3: add the following arguments to the setup() call in your setup.py:
+ version=versioneer.get_version(),
+ cmdclass=versioneer.get_cmdclass(),
+ 4: run 'setup.py update_files', which will create _version.py, and will
+ append the following to your __init__.py:
+ from _version import __version__
+ 5: modify your MANIFEST.in to include versioneer.py
+ 6: add both versioneer.py and the generated _version.py to your VCS
+"""
+
+import os, sys, re
+from distutils.core import Command
+from distutils.command.sdist import sdist as _sdist
+from distutils.command.build import build as _build
+
+versionfile_source = None
+versionfile_build = None
+tag_prefix = None
+parentdir_prefix = None
+
+VCS = "git"
+IN_LONG_VERSION_PY = False
+
+
+LONG_VERSION_PY = '''
+IN_LONG_VERSION_PY = True
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (build by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain. Generated by
+# versioneer-0.7+ (https://github.com/warner/python-versioneer)
+
+# these strings will be replaced by git during git-archive
+git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
+git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
+
+
+import subprocess
+import sys
+
+def run_command(args, cwd=None, verbose=False):
+ try:
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
+ except EnvironmentError:
+ e = sys.exc_info()[1]
+ if verbose:
+ print("unable to run %%s" %% args[0])
+ print(e)
+ return None
+ stdout = p.communicate()[0].strip()
+ if sys.version >= '3':
+ stdout = stdout.decode()
+ if p.returncode != 0:
+ if verbose:
+ print("unable to run %%s (error)" %% args[0])
+ return None
+ return stdout
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+ # the code embedded in _version.py can just fetch the value of these
+ # variables. When used from setup.py, we don't want to import
+ # _version.py, so we do it with a regexp instead. This function is not
+ # used from _version.py.
+ variables = {}
+ try:
+ for line in open(versionfile_source,"r").readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["full"] = mo.group(1)
+ except EnvironmentError:
+ pass
+ return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+ refnames = variables["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print("variables are unexpanded, not using")
+ return {} # unexpanded, so not in an unpacked git-archive tarball
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ for ref in list(refs):
+ if not re.search(r'\d', ref):
+ if verbose:
+ print("discarding '%%s', no digits" %% ref)
+ refs.discard(ref)
+ # Assume all version tags have a digit. git's %%d expansion
+ # behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us
+ # distinguish between branches and tags. By ignoring refnames
+ # without digits, we filter out many common branch names like
+ # "release" and "stabilization", as well as "HEAD" and "master".
+ if verbose:
+ print("remaining refs: %%s" %% ",".join(sorted(refs)))
+ for ref in sorted(refs):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print("picking %%s" %% r)
+ return { "version": r,
+ "full": variables["full"].strip() }
+ # no suitable tags, so we use the full revision id
+ if verbose:
+ print("no suitable tags, using full revision id")
+ return { "version": variables["full"].strip(),
+ "full": variables["full"].strip() }
+
+def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
+ # this runs 'git' from the root of the source tree. That either means
+ # someone ran a setup.py command (and this code is in versioneer.py, so
+ # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
+ # the source tree), or someone ran a project-specific entry point (and
+ # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
+ # containing directory is somewhere deeper in the source tree). This only
+ # gets called if the git-archive 'subst' variables were *not* expanded,
+ # and _version.py hasn't already been rewritten with a short version
+ # string, meaning we're inside a checked out source tree.
+
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # some py2exe/bbfreeze/non-CPython implementations don't do __file__
+ return {} # not always correct
+
+ # versionfile_source is the relative path from the top of the source tree
+ # (where the .git directory might live) to this file. Invert this to find
+ # the root from __file__.
+ root = here
+ if IN_LONG_VERSION_PY:
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ root = os.path.dirname(here)
+ if not os.path.exists(os.path.join(root, ".git")):
+ if verbose:
+ print("no .git in %%s" %% root)
+ return {}
+
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
+ cwd=root)
+ if stdout is None:
+ return {}
+ if not stdout.startswith(tag_prefix):
+ if verbose:
+ print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix))
+ return {}
+ tag = stdout[len(tag_prefix):]
+ stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+ if stdout is None:
+ return {}
+ full = stdout.strip()
+ if tag.endswith("-dirty"):
+ full += "-dirty"
+ return {"version": tag, "full": full}
+
+
+def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
+ if IN_LONG_VERSION_PY:
+ # We're running from _version.py. If it's from a source tree
+ # (execute-in-place), we can work upwards to find the root of the
+ # tree, and then check the parent directory for a version string. If
+ # it's in an installed application, there's no hope.
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # py2exe/bbfreeze/non-CPython don't have __file__
+ return {} # without __file__, we have no hope
+ # versionfile_source is the relative path from the top of the source
+ # tree to _version.py. Invert this to find the root from __file__.
+ root = here
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ # we're running from versioneer.py, which means we're running from
+ # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
+ here = os.path.abspath(sys.argv[0])
+ root = os.path.dirname(here)
+
+ # Source tarballs conventionally unpack into a directory that includes
+ # both the project name and a version string.
+ dirname = os.path.basename(root)
+ if not dirname.startswith(parentdir_prefix):
+ if verbose:
+ print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %%
+ (root, dirname, parentdir_prefix))
+ return None
+ return {"version": dirname[len(parentdir_prefix):], "full": ""}
+
+tag_prefix = "%(TAG_PREFIX)s"
+parentdir_prefix = "%(PARENTDIR_PREFIX)s"
+versionfile_source = "%(VERSIONFILE_SOURCE)s"
+
+def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
+ variables = { "refnames": git_refnames, "full": git_full }
+ ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
+ if not ver:
+ ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+ if not ver:
+ ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
+ verbose)
+ if not ver:
+ ver = default
+ return ver
+
+'''
+
+
+import subprocess
+import sys
+
+def run_command(args, cwd=None, verbose=False):
+ try:
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
+ except EnvironmentError:
+ e = sys.exc_info()[1]
+ if verbose:
+ print("unable to run %s" % args[0])
+ print(e)
+ return None
+ stdout = p.communicate()[0].strip()
+ if sys.version >= '3':
+ stdout = stdout.decode()
+ if p.returncode != 0:
+ if verbose:
+ print("unable to run %s (error)" % args[0])
+ return None
+ return stdout
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+ # the code embedded in _version.py can just fetch the value of these
+ # variables. When used from setup.py, we don't want to import
+ # _version.py, so we do it with a regexp instead. This function is not
+ # used from _version.py.
+ variables = {}
+ try:
+ for line in open(versionfile_source,"r").readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["full"] = mo.group(1)
+ except EnvironmentError:
+ pass
+ return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+ refnames = variables["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print("variables are unexpanded, not using")
+ return {} # unexpanded, so not in an unpacked git-archive tarball
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ for ref in list(refs):
+ if not re.search(r'\d', ref):
+ if verbose:
+ print("discarding '%s', no digits" % ref)
+ refs.discard(ref)
+ # Assume all version tags have a digit. git's %d expansion
+ # behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us
+ # distinguish between branches and tags. By ignoring refnames
+ # without digits, we filter out many common branch names like
+ # "release" and "stabilization", as well as "HEAD" and "master".
+ if verbose:
+ print("remaining refs: %s" % ",".join(sorted(refs)))
+ for ref in sorted(refs):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print("picking %s" % r)
+ return { "version": r,
+ "full": variables["full"].strip() }
+ # no suitable tags, so we use the full revision id
+ if verbose:
+ print("no suitable tags, using full revision id")
+ return { "version": variables["full"].strip(),
+ "full": variables["full"].strip() }
+
+def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
+ # this runs 'git' from the root of the source tree. That either means
+ # someone ran a setup.py command (and this code is in versioneer.py, so
+ # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
+ # the source tree), or someone ran a project-specific entry point (and
+ # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
+ # containing directory is somewhere deeper in the source tree). This only
+ # gets called if the git-archive 'subst' variables were *not* expanded,
+ # and _version.py hasn't already been rewritten with a short version
+ # string, meaning we're inside a checked out source tree.
+
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # some py2exe/bbfreeze/non-CPython implementations don't do __file__
+ return {} # not always correct
+
+ # versionfile_source is the relative path from the top of the source tree
+ # (where the .git directory might live) to this file. Invert this to find
+ # the root from __file__.
+ root = here
+ if IN_LONG_VERSION_PY:
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ root = os.path.dirname(here)
+ if not os.path.exists(os.path.join(root, ".git")):
+ if verbose:
+ print("no .git in %s" % root)
+ return {}
+
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
+ cwd=root)
+ if stdout is None:
+ return {}
+ if not stdout.startswith(tag_prefix):
+ if verbose:
+ print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
+ return {}
+ tag = stdout[len(tag_prefix):]
+ stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+ if stdout is None:
+ return {}
+ full = stdout.strip()
+ if tag.endswith("-dirty"):
+ full += "-dirty"
+ return {"version": tag, "full": full}
+
+
+def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
+ if IN_LONG_VERSION_PY:
+ # We're running from _version.py. If it's from a source tree
+ # (execute-in-place), we can work upwards to find the root of the
+ # tree, and then check the parent directory for a version string. If
+ # it's in an installed application, there's no hope.
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # py2exe/bbfreeze/non-CPython don't have __file__
+ return {} # without __file__, we have no hope
+ # versionfile_source is the relative path from the top of the source
+ # tree to _version.py. Invert this to find the root from __file__.
+ root = here
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ # we're running from versioneer.py, which means we're running from
+ # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
+ here = os.path.abspath(sys.argv[0])
+ root = os.path.dirname(here)
+
+ # Source tarballs conventionally unpack into a directory that includes
+ # both the project name and a version string.
+ dirname = os.path.basename(root)
+ if not dirname.startswith(parentdir_prefix):
+ if verbose:
+ print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
+ (root, dirname, parentdir_prefix))
+ return None
+ return {"version": dirname[len(parentdir_prefix):], "full": ""}
+
+import sys
+
+def do_vcs_install(versionfile_source, ipy):
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ run_command([GIT, "add", "versioneer.py"])
+ run_command([GIT, "add", versionfile_source])
+ run_command([GIT, "add", ipy])
+ present = False
+ try:
+ f = open(".gitattributes", "r")
+ for line in f.readlines():
+ if line.strip().startswith(versionfile_source):
+ if "export-subst" in line.strip().split()[1:]:
+ present = True
+ f.close()
+ except EnvironmentError:
+ pass
+ if not present:
+ f = open(".gitattributes", "a+")
+ f.write("%s export-subst\n" % versionfile_source)
+ f.close()
+ run_command([GIT, "add", ".gitattributes"])
+
+
+SHORT_VERSION_PY = """
+# This file was generated by 'versioneer.py' (0.7+) from
+# revision-control system data, or from the parent directory name of an
+# unpacked source archive. Distribution tarballs contain a pre-generated copy
+# of this file.
+
+version_version = '%(version)s'
+version_full = '%(full)s'
+def get_versions(default={}, verbose=False):
+ return {'version': version_version, 'full': version_full}
+
+"""
+
+DEFAULT = {"version": "unknown", "full": "unknown"}
+
+def versions_from_file(filename):
+ versions = {}
+ try:
+ f = open(filename)
+ except EnvironmentError:
+ return versions
+ for line in f.readlines():
+ mo = re.match("version_version = '([^']+)'", line)
+ if mo:
+ versions["version"] = mo.group(1)
+ mo = re.match("version_full = '([^']+)'", line)
+ if mo:
+ versions["full"] = mo.group(1)
+ return versions
+
+def write_to_version_file(filename, versions):
+ f = open(filename, "w")
+ f.write(SHORT_VERSION_PY % versions)
+ f.close()
+ print("set %s to '%s'" % (filename, versions["version"]))
+
+
+def get_best_versions(versionfile, tag_prefix, parentdir_prefix,
+ default=DEFAULT, verbose=False):
+ # returns dict with two keys: 'version' and 'full'
+ #
+ # extract version from first of _version.py, 'git describe', parentdir.
+ # This is meant to work for developers using a source checkout, for users
+ # of a tarball created by 'setup.py sdist', and for users of a
+ # tarball/zipball created by 'git archive' or github's download-from-tag
+ # feature.
+
+ variables = get_expanded_variables(versionfile_source)
+ if variables:
+ ver = versions_from_expanded_variables(variables, tag_prefix)
+ if ver:
+ if verbose: print("got version from expanded variable %s" % ver)
+ return ver
+
+ ver = versions_from_file(versionfile)
+ if ver:
+ if verbose: print("got version from file %s %s" % (versionfile, ver))
+ return ver
+
+ ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+ if ver:
+ if verbose: print("got version from git %s" % ver)
+ return ver
+
+ ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose)
+ if ver:
+ if verbose: print("got version from parentdir %s" % ver)
+ return ver
+
+ if verbose: print("got version from default %s" % ver)
+ return default
+
+def get_versions(default=DEFAULT, verbose=False):
+ assert versionfile_source is not None, "please set versioneer.versionfile_source"
+ assert tag_prefix is not None, "please set versioneer.tag_prefix"
+ assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix"
+ return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix,
+ default=default, verbose=verbose)
+def get_version(verbose=False):
+ return get_versions(verbose=verbose)["version"]
+
+class cmd_version(Command):
+ description = "report generated version string"
+ user_options = []
+ boolean_options = []
+ def initialize_options(self):
+ pass
+ def finalize_options(self):
+ pass
+ def run(self):
+ ver = get_version(verbose=True)
+ print("Version is currently: %s" % ver)
+
+
+class cmd_build(_build):
+ def run(self):
+ versions = get_versions(verbose=True)
+ _build.run(self)
+ # now locate _version.py in the new build/ directory and replace it
+ # with an updated value
+ target_versionfile = os.path.join(self.build_lib, versionfile_build)
+ print("UPDATING %s" % target_versionfile)
+ os.unlink(target_versionfile)
+ f = open(target_versionfile, "w")
+ f.write(SHORT_VERSION_PY % versions)
+ f.close()
+
+class cmd_sdist(_sdist):
+ def run(self):
+ versions = get_versions(verbose=True)
+ self._versioneer_generated_versions = versions
+ # unless we update this, the command will keep using the old version
+ self.distribution.metadata.version = versions["version"]
+ return _sdist.run(self)
+
+ def make_release_tree(self, base_dir, files):
+ _sdist.make_release_tree(self, base_dir, files)
+ # now locate _version.py in the new base_dir directory (remembering
+ # that it may be a hardlink) and replace it with an updated value
+ target_versionfile = os.path.join(base_dir, versionfile_source)
+ print("UPDATING %s" % target_versionfile)
+ os.unlink(target_versionfile)
+ f = open(target_versionfile, "w")
+ f.write(SHORT_VERSION_PY % self._versioneer_generated_versions)
+ f.close()
+
+INIT_PY_SNIPPET = """
+from ._version import get_versions
+__version__ = get_versions()['version']
+del get_versions
+"""
+
+class cmd_update_files(Command):
+ description = "modify __init__.py and create _version.py"
+ user_options = []
+ boolean_options = []
+ def initialize_options(self):
+ pass
+ def finalize_options(self):
+ pass
+ def run(self):
+ ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py")
+ print(" creating %s" % versionfile_source)
+ f = open(versionfile_source, "w")
+ f.write(LONG_VERSION_PY % {"DOLLAR": "$",
+ "TAG_PREFIX": tag_prefix,
+ "PARENTDIR_PREFIX": parentdir_prefix,
+ "VERSIONFILE_SOURCE": versionfile_source,
+ })
+ f.close()
+ try:
+ old = open(ipy, "r").read()
+ except EnvironmentError:
+ old = ""
+ if INIT_PY_SNIPPET not in old:
+ print(" appending to %s" % ipy)
+ f = open(ipy, "a")
+ f.write(INIT_PY_SNIPPET)
+ f.close()
+ else:
+ print(" %s unmodified" % ipy)
+ do_vcs_install(versionfile_source, ipy)
+
+def get_cmdclass():
+ return {'version': cmd_version,
+ 'update_files': cmd_update_files,
+ 'build': cmd_build,
+ 'sdist': cmd_sdist,
+ }