pax_global_header00006660000000000000000000000064131050715020014504gustar00rootroot0000000000000052 comment=ad72a9f305894ea0e65e5f70e4067d722556b83e gnome-keysign-0.9/000077500000000000000000000000001310507150200141305ustar00rootroot00000000000000gnome-keysign-0.9/.gitignore000066400000000000000000000002001310507150200161100ustar00rootroot00000000000000*.swp *.pyc *.bak /build /dist /gnome_keysign.egg-info .flatpak-builder/ /.coverage /.noseids .tox/ cover/ **.flatpak-builder/ gnome-keysign-0.9/.gitmodules000066400000000000000000000002241310507150200163030ustar00rootroot00000000000000[submodule "monkeysign"] path = monkeysign #url = https://github.com/muelli/monkeysign.git url = https://0xacab.org/monkeysphere/monkeysign.git gnome-keysign-0.9/COPYING000066400000000000000000001045131310507150200151670ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. 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 How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . gnome-keysign-0.9/MANIFEST.in000066400000000000000000000004641310507150200156720ustar00rootroot00000000000000# Apparently, package_data is bundled for bdists but not for sdists # For sdists, this MANIFEST.in seems to be used. # http://stackoverflow.com/a/14159430/2015768 include data/gnome-keysign.desktop data/gnome-keysign.svg data/gnome-keysign.appdata.xml include keysign/*.ui include COPYING include README.rst gnome-keysign-0.9/Makefile000066400000000000000000000000261310507150200155660ustar00rootroot00000000000000 clean: rm -f *.pyc gnome-keysign-0.9/README.rst000066400000000000000000000132731310507150200156250ustar00rootroot00000000000000GNOME Keysign ============= A tool for signing OpenPGP keys. Its purpose is to ease signing other peoples' keys. It is similar to caff, PIUS, or monkeysign. In fact, it is influenced a lot by these tools and either re-implements ideas or reuses code. Consider either of the above mentioned tools when you need a much more mature codebase. In contrast to caff or monkeysign, this tool enables you to sign a key without contacting a key server. It downloads an authenticated copy of the key from the other party. For now, the key is authenticated by its fingerprint which is securely transferred via a QR code. Alternatively, the user may type the fingerprint manually, assuming that it has been transferred securely via the audible channel. After having obtained an authentic copy of the key, its UIDs are signed. The signatures are then encrypted and sent via email. In contrast to monkeysign, xdg-email is used to pop up a pre-filled email composer windows of the mail client the user has configured to use. This greatly reduces complexity as no SMTP configuration needs to be obtained and gives the user a well known interface. Installation ============= Before you can install GNOME Keysign, you need to have a few dependencies installed. The list of dependencies includes: * avahi with python bindings * dbus with python bindings * GStreamer with the good and bad plugins * GTK and Cairo * gobject introspection for those libraries openSUSE installation ---------------------- openSUSE has `packaged the application `_ so it should be easy for you to install it. Debian and Ubuntu dependencies --------------------------------- This list of packages seems to make it work: python avahi-daemon python-avahi python-gi gir1.2-glib-2.0 gir1.2-gtk-3.0 python-dbus gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0 gstreamer1.0-plugins-bad gstreamer1.0-plugins-good python-gi-cairo In Ubuntu, the package gstreamer1.0-plugins-bad provides the zbar and the gtksink element, and gstreamer1.0-plugins-good provides the autovideosrc element. These packages should be optional: python-requests monkeysign python-qrcode Fedora dependencies -------------------- The following has worked at least once for getting the application running, assuming that pip and git are already installed: .. code:: sudo dnf install -y python-gobject python-avahi dbus-python gstreamer1-plugins-bad-free-extras gstreamer1-plugins-good gnupg Installation with pip ----------------------- You may try the following in order to install the program to your user's home directory. .. code:: pip install --user 'git+https://github.com/GNOME-Keysign/gnome-keysign.git#egg=gnome-keysign' You should find a script in ~/.local/bin/gnome-keysign as well as a .desktop launcher in ~/.local/share/applications/. From git --------- If you intend to hack on the software (*yay*!), you may want to clone the repository and install from there. .. code:: git clone --recursive https://github.com/gnome-keysign/gnome-keysign.git cd gnome-keysign virtualenv --system-site-packages --python=python2 /tmp/keysign /tmp/keysign/bin/pip install . Note that this installs the application in the virtual environment, so you run the program from there, e.g. /tmp/keysign/bin/gnome-keysign. Starting ========= If you have installed the application with pip, a .desktop file should have been deployed such that you should be able to run the program from your desktop shell. Search for "Keysign". If you want to run the program from the command line, you can add ~/.local/bin to your PATH. The installation should have put an executable named keysign in that directory. If you haven't installed via pip or not to your user's home directory (i.e. with --user), you can start the program from your environment's ./bin/ directory. Running ======= Server side ----------- This describes running the application's server mode in order to allow you to have your key signed by others running the application in client mode. Once you've fired up the application, you can see a list of your private keys. Select one and the application will advance to the next stage. You will see the details of the key you've selected. If you are happy with the key you have selected, click "Next". This will cause the key's availability to be published on the local network. Also, a HTTP server will be spawned in order to enable others to download your key. In order for others to find you, the app displays both a string identifying your key and a bar code. Either share the string or the bar code with someone who wants to sign your key. Client side ----------- Here, the client side is described. This is to sign someone's key. You are presented with feed of your camera and an entry field to type in a string. If you meet someone who has the server side of the application running, you can scan the bar code present at the other party. After you either typed a fingerprint or scanned a bar code, the program will look for the relevant key on your local network. Note that you've transmitted the fingerprint securely, i.e. via a visual channel in form of a bar code or the displayed fingerprint. This data allows to find the correct key. In fact, the client tries to find the correct key by comparing the fingerprint of the keys available on the local network. After the correct key has been found, you see details of the key to be signed. If you are happy with what you see, i.e. because you have checked the names on the key to be correct, you can click next. This will cause the program to sign the key and open your mail program with the encrypted signature preloaded as attachment. gnome-keysign-0.9/RELEASE_NOTES000066400000000000000000000021651310507150200161070ustar00rootroot00000000000000GNOME Keysign is a tool to make signing OpenPGP keys as easy as possible. This is the v0.9 release which makes use of Glade based widgets and fixes a few important bugs. You can get the app from: https://github.com/GNOME-Keysign/gnome-keysign/ Changes ========== * Widgets are now loaded from Glade files instead of created from Python code * The key downloader returns bytes rather than strings * Keyserver: Now using application/pgp-keys as MIME type * Barcode scanner: Removed GStreamer<1.6 compatibility * Barcode scanner: Moved to gtksink for reducing code and increasing compatibility with running in a VM * Barcode scanner: Moved to autovideosrc * Barcode scanner: Stopped logging every single message * ScalingImage: Respecting the height when calculating the scale * KeysPage: Renamed signals to match Gtk convention more closely * Make it find the relevant key faster by ordering the list of servers before attempting to download Resources ========= Download: https://github.com/GNOME-Keysign/gnome-keysign/archive/0.9.tar.gz Web site: https://wiki.gnome.org/GnomeKeysign gnome-keysign-0.9/data/000077500000000000000000000000001310507150200150415ustar00rootroot00000000000000gnome-keysign-0.9/data/gnome-keysign.appdata.xml000066400000000000000000000014501310507150200217500ustar00rootroot00000000000000 gnome-keysign.desktop CC0 <_p> GNOME-Keysign allows signing OpenPGP keys comfortably. <_p> It can scan another key's barcode and transfer the key securely, allowing for casual two-party key signing sessions. It follows best practises by sending the encrypted signatures to the UIDs of a key using the Email client the user configured to use. https://wiki.gnome.org/GnomeKeysign?action=AttachFile&do=get&target=UI.png https://wiki.gnome.org/GnomeKeysign tobiasmue@gnome.org gnome-keysign-0.9/data/gnome-keysign.desktop000066400000000000000000000004151310507150200212100ustar00rootroot00000000000000[Desktop Entry] Name=Keysign Comment=A keysigning helper to enable you to comfortably exchange OpenPGP keys with a friend Keywords=python;gpg;gnupg;key;openpgp; Type=Application Exec=python -m keysign Icon=gnome-keysign Categories=GTK;GNOME;Utility; StartupNotify=true gnome-keysign-0.9/data/gnome-keysign.svg000066400000000000000000003703501310507150200203460ustar00rootroot00000000000000 image/svg+xml gnome-keysign-0.9/gnome-keysign.py000077500000000000000000000005351310507150200172640ustar00rootroot00000000000000#!/usr/bin/env python2 import logging, os, sys, signal logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') thisdir = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, thisdir) sys.path.insert(0, os.sep.join((thisdir, 'monkeysign'))) from keysign import main sys.exit(main()) gnome-keysign-0.9/keysign/000077500000000000000000000000001310507150200156015ustar00rootroot00000000000000gnome-keysign-0.9/keysign/GPGQRCode.py000077500000000000000000000053251310507150200176360ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . """This is a very simple QR Code generator which scans your GnuPG keyring for keys and selects the one matching your input """ import logging import os import sys import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .gpgmh import get_usable_keys if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .QRCode import QRImage def main(): import sys key = sys.argv[1] # Heh, we take the first key here. Maybe we should raise a warning # or so, when there is more than one key. key = list(get_usable_keys(pattern=key))[0] fpr = key.fingerprint data = 'OPENPGP4FPR:' + fpr w = Gtk.Window() w.connect("delete-event", Gtk.main_quit) w.set_default_size(100,100) v = Gtk.VBox() label = Gtk.Label(data) qr = QRImage(data) v.add(label) v.add(qr) w.add(v) w.show_all() Gtk.main() if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') main() gnome-keysign-0.9/keysign/GtkKeyserver.py000066400000000000000000000066131310507150200206060ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # Copyright 2014 Andrei Macavei # Copyright 2014 Srdjan Grubor # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . '''This is an exercise to see how we can combine Python threads with the Gtk mainloop ''' import logging import os import sys from threading import Thread from gi.repository import GLib from gi.repository import Gtk from dbus.mainloop.glib import DBusGMainLoop if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from . import Keyserver class ServerWindow(Gtk.Window): def __init__(self): self.log = logging.getLogger(__name__) Gtk.Window.__init__(self, title="Gtk and Python threads") self.set_border_width(10) self.connect("delete-event", Gtk.main_quit) hBox = Gtk.HBox() self.button = Gtk.ToggleButton('Start') hBox.pack_start(self.button, False, False, 0) self.add(hBox) self.button.connect('toggled', self.on_button_toggled) #GLib.idle_add(self.setup_server) def on_button_toggled(self, button): self.log.debug('toggled button') if button.get_active(): self.log.debug("I am being switched on") self.setup_server() else: self.log.debug("I am being switched off") self.stop_server() def setup_server(self): self.log.info('Serving now') self.log.debug('About to call %r', Keyserver.ServeKeyThread) self.keyserver = Keyserver.ServeKeyThread('Keydata', 'fingerprint') self.log.info('Starting thread %r', self.keyserver) self.keyserver.start() self.log.info('Finsihed serving') return False def stop_server(self): self.keyserver.shutdown() def main(args): log = logging.getLogger(__name__) log.debug('Running main with args: %s', args) w = ServerWindow() w.show_all() log.debug('Starting main') DBusGMainLoop(set_as_default = True) Gtk.main() if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') # From http://stackoverflow.com/a/16486080/2015768 import signal signal.signal(signal.SIGINT, signal.SIG_DFL) sys.exit(main(sys.argv)) gnome-keysign-0.9/keysign/KeyPresent.py000066400000000000000000000200571310507150200202500ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # Copyright 2014 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import signal import sys import argparse import logging import os from gi.repository import Gtk, GLib if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .__init__ import __version__ from .gpgmh import get_usable_keys from .QRCode import QRImage from .util import format_fingerprint log = logging.getLogger(__name__) class KeyPresentWidget(Gtk.Widget): """A widget for presenting a gpgmh.Key It shows details of the given key and customizable data in a qrcode encoded in a QRImage. The widget takes a full key object, rather than a fingerprint, because it makes assumptions about the key object anyway. So it can as well take it directly and enable higher level controllers to deal with the situation that a given fingerprint, which really is a search string for gpg, yields multiple results. """ def __new__(cls, *args, **kwargs): thisdir = os.path.dirname(os.path.abspath(__file__)) builder = kwargs.pop("builder", None) if not builder: builder = Gtk.Builder() builder.add_objects_from_file( os.path.join(thisdir, 'send.ui'), ['box3'] ) log.debug("Our builder is: %r", builder) # The widget will very likely have a parent. # Gtk doesn't like so much adding a Widget to a container # when the widget already has been added somewhere. # So we get the parent widget and remove the child. w = builder.get_object('box3') parent = w.get_parent() if parent: parent.remove(w) w._builder = builder w.__class__ = cls return w def __init__(self, key, qrcodedata=None, builder=None): """A new KeyPresentWidget shows the string you provide as qrcodedata in a qrcode. If it evaluates to False, the key's fingerprint will be shown. That is, "OPENPGP4FPR: + fingerprint. """ self.key_id_label = self._builder.get_object("keyidLabel") self.uids_label = self._builder.get_object("uidsLabel") self.fingerprint_label = self._builder.get_object("keyFingerprintLabel") self.qrcode_frame = self._builder.get_object("qrcode_frame") self.key_id_label.set_markup( format_fingerprint(key.fingerprint).replace('\n', ' ')) self.uids_label.set_markup("\n".join( [GLib.markup_escape_text("{}".format(uid)) for uid in key.uidslist])) self.fingerprint_label.set_markup(format_fingerprint(key.fingerprint)) if not qrcodedata: qrcodedata = "OPENPGP4FPR:" + key.fingerprint self.qrcode_frame.add(QRImage(qrcodedata)) self.qrcode_frame.show_all() class KeyPresent(Gtk.Application): """A demo application showing how to display sufficient details about a key such that it can be sent securely. Note that the main purpose is to enable secure transfer, not reviewing key details. As such, the implementation might change a lot, depending on the method of secure transfer. """ def __init__(self, *args, **kwargs): #super(Keys, self).__init__(*args, **kwargs) Gtk.Application.__init__( self, application_id="org.gnome.keysign.keypresent") self.connect("activate", self.on_activate) self.connect("startup", self.on_startup) self.log = logging.getLogger(__name__) self.key_present_page = None def on_quit(self, app, param=None): self.quit() def on_startup(self, app): self.log.info("Startup") self.window = Gtk.ApplicationWindow(application=app) self.window.set_title ("Keysign - Key") self.window.add(self.key_present_page) def on_activate(self, app): self.log.info("Activate!") #self.window = Gtk.ApplicationWindow(application=app) self.window.show_all() # In case the user runs the application a second time, # we raise the existing window. self.window.present() def run(self, args): log.debug("running: %s", args) fpr = args key = next(iter(get_usable_keys(pattern=fpr))) self.key_present_page = KeyPresentWidget(key) super(KeyPresent, self).run() def parse_command_line(argv): """Parse command line argument. See -h option :param argv: arguments on the command line must include caller file name. """ formatter_class = argparse.RawDescriptionHelpFormatter parser = argparse.ArgumentParser(description='Auxiliary helper program '+ 'to present a key', formatter_class=formatter_class) parser.add_argument("--version", action="version", version="%(prog)s {}".format(__version__)) parser.add_argument("-v", "--verbose", dest="verbose_count", action="count", default=0, help="increases log verbosity for each occurence.") #parser.add_argument("-g", "--gpg", # action="store_true", default=False, # help="Use local GnuPG Keyring instead of file.") #parser.add_argument('-o', metavar="output", # type=argparse.FileType('w'), default=sys.stdout, # help="redirect output to a file") #parser.add_argument('file', help='File to read keydata from ' + # '(or KeyID if --gpg is given)') parser.add_argument('fpr', help='The fingerprint of the key to transfer') ## nargs='+', # argparse.REMAINDER, #parser.add_argument('input', metavar="input", ## nargs='+', # argparse.REMAINDER, #help="input if any...") arguments = parser.parse_args(argv[1:]) # Sets log level to WARN going more verbose for each new -v. log.setLevel(max(3 - arguments.verbose_count, 0) * 10) return arguments def main(args=sys.argv): """This is an example program of how to use the PresentKey widget""" logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') try: arguments = parse_command_line(args) #if arguments.gpg: # keydata = export_keydata(next(get_usable_keys(keyid))) #else: # keydata = open(arguments.file, 'r').read() fpr = arguments.fpr app = KeyPresent() try: GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: pass exit_status = app.run(fpr) return exit_status finally: logging.shutdown() if __name__ == "__main__": sys.exit(main()) gnome-keysign-0.9/keysign/KeysPage.py000066400000000000000000000302751310507150200176720ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # Copyright 2014 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from datetime import datetime import signal import sys import argparse import logging from gi.repository import Gtk, GLib from gi.repository import GObject from .gpgmh import get_usable_secret_keys, get_usable_keys # These are relative imports from __init__ import __version__ log = logging.getLogger(__name__) class KeysPage(Gtk.VBox): '''This represents a list of keys with the option for the user to select one key to proceed. This class emits a `key-selected' signal when the user initially selects a key such that it is highlighted. Analogous to a ListBox, the `key-activated' signal is emitted when the user commits to a key, i.e. by pressing a designated button to make the selection public. ''' __gsignals__ = { str('key-activated'): (GObject.SIGNAL_RUN_LAST, None, # the activated key object (object,)), str('key-selected'): (GObject.SIGNAL_RUN_LAST, None, # the selected key object (object,)), } def __init__(self, show_public_keys=False): '''Sets the widget up. The show_public_keys parameter is meant for development purposes only. If set to True, the widget will show the public keys, too. Otherwise, secret keys are shown. ''' super(KeysPage, self).__init__() # set up the list store to be filled up with user's gpg keys # Note that other functions expect a certain structure to # this ListStore, e.g. when parsing the selection of the # TreeView, i.e. in get_items_from_selection. self.store = Gtk.ListStore(str, str, str) # name, email, fingerprint keys = get_usable_secret_keys() keys += get_usable_keys() if show_public_keys else [] for key in keys: uidslist = key.uidslist #UIDs: Real Name (Comment) fingerprint = key.fingerprint for uid in uidslist: self.store.append((uid.name, uid.email, fingerprint)) if len(self.store) == 0: self.pack_start(Gtk.Label("You don't have a private key"), True, True, 0) else: # create the tree view self.treeView = Gtk.TreeView(model=self.store) # setup 'Name' column nameRenderer = Gtk.CellRendererText() nameColumn = Gtk.TreeViewColumn("Name", nameRenderer, text=0) # setup 'Email' column emailRenderer = Gtk.CellRendererText() emailColumn = Gtk.TreeViewColumn("Email", emailRenderer, text=1) ## setup 'Fingerprint' column # keyRenderer = Gtk.CellRendererText() # keyColumn = Gtk.TreeViewColumn("Fingerprint", keyRenderer, text=2) self.treeView.append_column(nameColumn) self.treeView.append_column(emailColumn) # self.treeView.append_column(keyColumn) self.treeView.connect('row-activated', self.on_row_activated) # make the tree view resposive to single click selection self.treeView.get_selection().connect('changed', self.on_selection_changed) # make the tree view scrollable self.scrolled_window = Gtk.ScrolledWindow() self.scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.scrolled_window.add(self.treeView) self.scrolled_window.set_min_content_height(200) #self.pack_start(self.scrolled_window, True, True, 0) self.hpane = Gtk.HPaned() self.hpane.pack1(self.scrolled_window, False, False) self.right_pane = Gtk.VBox() right_label = Gtk.Label(label='Select key on the left') self.right_pane.add(right_label) # Hm, right now, the width of the right pane changes, when # a key is selected, because the right pane's content will be # wider when it displays expiration et al. # Can we hint at that fact and make the VBox a bit wider than necessary? #padded_label = Gtk.Label(label='Select key on the left'*3) #self.right_pane.add(padded_label) self.hpane.pack2(self.right_pane, True, False) self.pack_start(self.hpane, True, True, 0) # We could make it a @staticmethod, but the returned items # are bound to the model, anyway. So it probably doesn't # make much sense to have a static function, anyway. def get_items_from_selection(self, selection=None): '''Returns the elements in the ListStore for the given selection''' s = selection or self.treeView.get_selection() model, paths = s.get_selected_rows() name = email = fingerprint = None for path in paths: iterator = model.get_iter(path) (name, email, fingerprint) = model.get(iterator, 0, 1, 2) break return (name, email, fingerprint) def on_selection_changed(self, selection, *args): log.debug('Selected new TreeView item %s = %s', selection, args) name, email, fingerprint = \ self.get_items_from_selection(selection)[:3] # FIXME: We'd rather want to get the key object # (or its representation) from the model, not by querying again key = next(iter(get_usable_keys(pattern=fingerprint))) self.emit('key-selected', key) exp_date = key.expiry if exp_date is None: expiry = "No expiration date" else: expiry = "{:%Y-%m-%d %H:%M:%S}".format(exp_date) pane = self.right_pane for child in pane.get_children(): # Ouch, this is not very efficient. # But this deals with the fact that the first # label in the pane is a "Select a key on the left" # text. pane.remove(child) ctx = {'keyid':fingerprint[-8:], 'expiry':expiry, 'sigs':'', 'fingerprint':fingerprint} keyid_label = Gtk.Label(label='Key {keyid}'.format(**ctx)) expiration_label = Gtk.Label(label='Expires: {expiry}'.format(**ctx)) #signatures_label = Gtk.Label(label='{sigs} signatures'.format(**ctx)) publish_button = Gtk.Button(label='Go ahead!'.format(**ctx)) publish_button.connect('clicked', self.on_publish_button_clicked, key) for w in (keyid_label , expiration_label #, signatures_label , publish_button ): pane.add(w) pane.show_all() def on_row_activated(self, treeview, tree_path, column): '''A callback for when the user "activated" a row, e.g. by double-clicking an entry. It emits the key-selected signal. ''' # We just hijack the existing function. # I'm sure we could get the required information out of # the tree_path and column, but I don't know how. name, email, fingerprint = \ self.get_items_from_selection()[:3] key = next(iter(get_usable_keys(pattern=fingerprint))) log.info("keys: %r", get_usable_keys(pattern=fingerprint)) log.info("Emitting %r", key) self.emit('key-activated', key) def on_publish_button_clicked(self, button, key, *args): '''Callback for when the user has expressed their wish to publish a key on the network. It will emit a "key-selected" signal with the ID of the selected key.''' log.debug('Clicked publish for key (%s) %s (%s)', type(key), key, args) fingerprint = key.fingerprint self.emit('key-activated', key) class Keys(Gtk.Application): """A widget which displays keys in a user's Keyring. Once the user has selected a key, the key-selected signal will be thrown. """ def __init__(self, *args, **kwargs): #super(Keys, self).__init__(*args, **kwargs) Gtk.Application.__init__( self, application_id="org.gnome.keysign.keys") self.connect("activate", self.on_activate) self.connect("startup", self.on_startup) self.log = logging.getLogger(__name__) self.keys_page = KeysPage() self.keys_page.connect('key-selection-changed', self.on_key_selection_changed) self.keys_page.connect('key-selected', self.on_key_selected) def on_quit(self, app, param=None): self.quit() def on_startup(self, app): self.log.info("Startup") self.window = Gtk.ApplicationWindow(application=app) self.window.set_title ("Keysign - Keys") self.window.add(self.keys_page) def on_activate(self, app): self.log.info("Activate!") #self.window = Gtk.ApplicationWindow(application=app) self.window.show_all() # In case the user runs the application a second time, # we raise the existing window. self.window.present() def on_key_selection_changed(self, button, key): """This is the connected to the KeysPage's key-selection-changed signal As a user of that widget, you would show more details in the GUI or prepare for a final commitment by the user. """ self.log.info('Selection changed to: %s', key) def on_key_selected(self, button, fpr): """This is the connected to the KeysPage's key-selected signal As a user of that widget, you would enable buttons or proceed with the GUI. """ self.log.info('User committed to a key! %s', fpr) def parse_command_line(argv): """Parse command line argument. See -h option :param argv: arguments on the command line must include caller file name. """ formatter_class = argparse.RawDescriptionHelpFormatter parser = argparse.ArgumentParser(description='Auxiliary helper program '+ 'display keys', formatter_class=formatter_class) parser.add_argument("--version", action="version", version="%(prog)s {}".format(__version__)) parser.add_argument("-v", "--verbose", dest="verbose_count", action="count", default=0, help="increases log verbosity for each occurence.") #parser.add_argument('-o', metavar="output", # type=argparse.FileType('w'), default=sys.stdout, # help="redirect output to a file") #parser.add_argument('input', metavar="input", ## nargs='+', # argparse.REMAINDER, #help="input if any...") arguments = parser.parse_args(argv[1:]) # Sets log level to WARN going more verbose for each new -v. log.setLevel(max(3 - arguments.verbose_count, 0) * 10) return arguments def main(args=sys.argv): """This is an example program of how to use the Keys widget""" logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') try: arguments = parse_command_line(args) app = Keys() try: GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: pass exit_status = app.run(None) return exit_status finally: logging.shutdown() if __name__ == "__main__": sys.exit(main()) gnome-keysign-0.9/keysign/Keyserver.py000077500000000000000000000206051310507150200201400ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # Copyright 2014 Andrei Macavei # Copyright 2015 Jody Hansen # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . try: from http.server import BaseHTTPRequestHandler, HTTPServer from socketserver import ThreadingMixIn except ImportError: from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from SocketServer import ThreadingMixIn import logging import os import socket from threading import Thread # This is probably really bad... But doing relative imports only # works for modules. However, I want to be able to call this Keyserver.py # for testing purposes. if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .__init__ import __version__ from .network.AvahiPublisher import AvahiPublisher from .gpgmh import fingerprint_from_keydata log = logging.getLogger(__name__) class KeyRequestHandlerBase(BaseHTTPRequestHandler): '''This is the "base class" which needs to be given access to the key to be served. So you will not use this class, but create a use one inheriting from this class. The subclass must also define a keydata field. ''' server_version = 'GNOME-Keysign/' + '%s' % __version__ # As per RFC 2015 Section 7 # https://tools.ietf.org/html/rfc2015#section-7 ctype = 'application/pgp-keys' def do_GET(self): f = self.send_head(self.keydata) self.wfile.write(self.keydata) def send_head(self, keydata=None): kd = keydata if keydata else self.keydata self.send_response(200) self.send_header('Content-Type', self.ctype) self.send_header('Content-Length', len(kd)) self.end_headers() return kd class ThreadedKeyserver(ThreadingMixIn, HTTPServer): '''The keyserver in a threaded fashion''' address_family = socket.AF_INET6 def __init__(self, server_address, *args, **kwargs): if issubclass(self.__class__, object): super(ThreadedKeyserver, self).__init__(*args, **kwargs) else: HTTPServer.__init__(self, server_address, *args, **kwargs) # WTF? There is no __init__..? # ThreadingMixIn.__init__(self, server_address, *args, **kwargs) def server_bind(self): # Override this method to be sure v6only is false: we want to # listen to both IPv4 and IPv6! self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) HTTPServer.server_bind(self) class ServeKeyThread(Thread): '''Serves requests and manages the server in separates threads. You can create an object and call start() to let it run. If you want to stop serving, call shutdown(). ''' def __init__(self, data, fpr, port=9001, *args, **kwargs): '''Initializes the server to serve the data''' self.keydata = data self.fpr = fpr self.port = port super(ServeKeyThread, self).__init__(*args, **kwargs) self.daemon = True self.httpd = None def start(self, data=None, fpr=None, port=None, *args, **kwargs): '''This is run in the same thread as the caller. This calls run() in a separate thread. In order to resolve DBus issues, most things are done here. However, you probably need to start dbus.mainloop.glib.DBusGMainLoop (set_as_default=True) in order for this work. ''' port = port or self.port or 9001 fpr = fpr or self.fpr tries = 10 kd = data if data else self.keydata class KeyRequestHandler(KeyRequestHandlerBase): '''You will need to create this during runtime''' keydata = kd HandlerClass = KeyRequestHandler for port_i in (port + p for p in range(tries)): try: log.info('Trying port %d', port_i) server_address = ('', port_i) self.httpd = ThreadedKeyserver(server_address, HandlerClass, **kwargs) ### # This is a bit of a hack, it really should be # in some lower layer, such as the place were # the socket is created and listen()ed on. service_txt = { 'fingerprint': fpr, 'version': __version__, } log.info('Requesting Avahi with txt: %s', service_txt) self.avahi_publisher = ap = AvahiPublisher( service_port = port_i, service_name = 'HTTP Keyserver %s' % fpr, service_txt = service_txt, # self.keydata is too big for Avahi; it crashes service_type = '_gnome-keysign._tcp', ) log.info('Trying to add Avahi Service') ap.add_service() except socket.error as value: errno = value.errno if errno == 10054 or errno == 32: # This seems to be harmless break else: break finally: pass super(ServeKeyThread, self).start(*args, **kwargs) def serve_key(self, poll_interval=0.15): '''An HTTPd is started and being put to serve_forever. You need to call shutdown() in order to stop serving. ''' #sa = self.httpd.socket.getsockname() try: log.info('Serving now on %s, this is probably blocking...', self.httpd.socket.getsockname()) self.httpd.serve_forever(poll_interval=poll_interval) finally: log.info('finished serving') #httpd.dispose() def run(self): '''This is being run by Thread in a separate thread after you call start()''' self.serve_key() def shutdown(self): '''Sends shutdown to the underlying httpd''' log.info("Removing Avahi Service") self.avahi_publisher.remove_service() log.info("Shutting down httpd %r", self.httpd) self.httpd.shutdown() if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) import dbus, time dbus.mainloop.glib.DBusGMainLoop (set_as_default=True) def stop_thread(t, seconds=5): log.info('Sleeping %d seconds, then stopping', seconds) time.sleep(seconds) t.shutdown() import sys if len(sys.argv) >= 2: fname = sys.argv[1] KEYDATA = open(fname, 'r').read() # FIXME: Someone needs to determine the fingerprint # of the data just read fpr = fingerprint_from_keydata(KEYDATA) else: KEYDATA = 'Example data' fpr = ''.join('F289 F7BA 977D F414 3AE9 FDFB F70A 0290 6C30 1813'.split()) if len(sys.argv) >= 3: timeout = int(sys.argv[2]) else: timeout = 5 t = ServeKeyThread(KEYDATA, fpr) stop_t = Thread(target=stop_thread, args=(t,timeout)) stop_t.daemon = True t.start() stop_t.start() while True: log.info('joining stop %s', stop_t.isAlive()) stop_t.join(1) log.info('joining t %s', t.isAlive()) t.join(1) if not t.isAlive() or not stop_t.isAlive(): break log.warn('Last line') gnome-keysign-0.9/keysign/QRCode.py000077500000000000000000000243131310507150200172760ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # Copyright 2015 Benjamin Berg # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import gi gi.require_version('Gtk', '3.0') from gi.repository import Gdk, Gtk, GObject import qrcode import cairo log = logging.getLogger(__name__) class QRImage(Gtk.DrawingArea): """An Image encoding data as a QR Code. The image tries to scale as big as possible. """ def __init__(self, data='Default String', handle_events=True, background=0xff, *args, **kwargs): """The QRImage widget inherits from Gtk.Image, but it probably cannot be used as one, as there is an event handler for resizing events which will overwrite to currently loaded image. You made set data now, or later simply via the property. handle_events can be set to False if the fullscreen window should not be created on click. The background can be set to 0x00 (or 0xff) creating a black (or white) background onto which the code is rendered. """ super(QRImage, self).__init__(*args, **kwargs) self.log = logging.getLogger(__name__) self.background = background # We invert the background self.foreground = 0xff ^ background # The data to be rendered self._surface = None self.data = data self.set_app_paintable(True) self.handle_events = handle_events if handle_events: self.connect('button-release-event', self.on_button_released) self.add_events( Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK) def on_button_released(self, widget, event): self.log.info('Event %s', dir(event)) if event.button == 1: w = FullscreenQRImageWindow(data=self.data) top_level_window = self.get_toplevel() if top_level_window.is_toplevel(): w.set_transient_for(top_level_window) def do_size_allocate(self, event): """This is the event handler for the resizing event, i.e. when window is resized. We then want to regenerate the QR code. """ allocation = self.get_allocation() if allocation != event: self.queue_draw() Gtk.DrawingArea.do_size_allocate(self, event) def do_draw(self, cr): """This scales the QR Code up to the widget's size. You may define your own size, but you must be careful not to cause too many resizing events. When you request a too big size, it may loop to death trying to fit the image. """ data = self.data box = self.get_allocation() width, height = box.width, box.height size = min(width, height) qrcode = self.qrcode img_size = qrcode.get_width() cr.save() background = self.background foreground = self.foreground # This seems to set tje background, # but I'm not sure... cr.set_source_rgb(background, background, background) #cr.fill() # And have it painted cr.paint() # Now, I think we set the colour of the turtle # paint whatever is coming next. cr.set_source_rgb(foreground, foreground, foreground) # All of the rest I do not really understand, # but it seems to work reasonably well, without # weird PIL to Pixbuf hacks. cr.translate(width / 2, height / 2) scale = max(1, size / img_size) cr.scale(scale, scale) cr.translate(-img_size / 2, -img_size / 2) pattern = cairo.SurfacePattern(qrcode) pattern.set_filter(cairo.FILTER_NEAREST) cr.mask(pattern) cr.restore() def create_qrcode(self, data): log.debug('Encoding %s', data) code = qrcode.QRCode() code.add_data(data) matrix = code.get_matrix() size = len(matrix) stride = (size + 3) / 4 * 4 data = bytearray(stride * size) background = self.background foreground = self.foreground for x in range(size): for y in range(size): # Here we seem to be defining what # is going to be put on the surface. # I don't know what the semantic is, # though. Is 0 black? Or no modification # of the underlying background? # Anyway, this give us a nice white # QR Code. Note that we do [y][x], # otherwise the generated code is diagonally # mirrored. if matrix[y][x]: data[x + y * stride] = background else: data[x + y * stride] = foreground surface = cairo.ImageSurface.create_for_data(data, cairo.FORMAT_A8, size, size, stride) return surface @property def qrcode(self): if self._surface is not None: return self._surface self._surface = self.create_qrcode(self.data) return self._surface def set_data(self, data): # FIXME: Full screen window is not updated in here ... self._data = data self._surface = None size = self.qrcode.get_width() self.set_size_request(size, size) self.queue_draw() self.set_tooltip_text(data) def get_data(self): return self._data data = GObject.property(getter=get_data, setter=set_data) def fullscreen_at_monitor(window, n): """Fullscreens a given window on the n-th monitor This is because Gtk's fullscreen_on_monitor seems to be buggy. http://stackoverflow.com/a/39386341/2015768 """ screen = Gdk.Screen.get_default() monitor_n_geo = screen.get_monitor_geometry(n) x = monitor_n_geo.x y = monitor_n_geo.y window.move(x,y) window.fullscreen() class FullscreenQRImageWindow(Gtk.Window): '''Displays a QRImage in a fullscreen window The window is supposed to close itself when a button is clicked.''' def __init__(self, data, *args, **kwargs): '''The data will be passed to the QRImage''' self.log = logging.getLogger(__name__) if issubclass(self.__class__, object): super(FullscreenQRImageWindow, self).__init__(*args, **kwargs) else: Gtk.Window.__init__(*args, **kwargs) self.fullscreen() self.qrimage = QRImage(data=data, handle_events=False) self.qrimage.set_has_tooltip(False) self.add(self.qrimage) self.connect('button-release-event', self.on_button_released) self.connect('key-release-event', self.on_key_released) self.add_events( Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK ) self.show_all() def on_button_released(self, widget, event): '''Connected to the button-release-event and closes this window''' # It's unclear whether all resources are free()d self.log.info('Event on fullscreen: %s', event) if event.button == 1: self.unfullscreen() self.hide() self.close() def on_key_released(self, widget, event): self.log.info('Event on fullscreen: %s', dir(event)) self.log.info('keycode: %s', event.get_keycode()) self.log.info('keyval: %s', event.get_keyval()) self.log.info('keyval: %s', Gdk.keyval_name(event.keyval)) keyname = Gdk.keyval_name(event.keyval).lower() if keyname == 'escape' or keyname == 'f' or keyname == 'q': self.unfullscreen() self.hide() self.close() elif keyname == 'left' or keyname == 'right': # We're trying to switch monitors screen = self.get_screen() # Determines the monitor the window is currently most visible in n = screen.get_monitor_at_window(screen.get_active_window()) n_monitors = screen.get_n_monitors() if keyname == 'left': delta = -1 elif keyname == 'right': delta = 1 else: raise ValueError() new_n = (n+delta) % n_monitors log.info("Moving from %d to %d/%d", n, new_n, n_monitors) if n != new_n: # This call would make it animate a little, # but it looks weird for me, so we don't unfullscreen. # self.unfullscreen() fullscreen_at_monitor(self, new_n) # The following call is broken, unfortunately. # https://bugzilla.gnome.org/show_bug.cgi?id=752677 # self.fullscreen_on_monitor(self.get_screen(), new_n) def main(data): w = Gtk.Window() w.connect("delete-event", Gtk.main_quit) w.set_default_size(100,100) qr = QRImage(data) global fullscreen fullscreen = False def on_released(widget, event): global fullscreen if event.button == 1: fullscreen = not fullscreen if fullscreen: w.fullscreen() else: w.unfullscreen() #qr.connect('button-release-event', on_released) #qr.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK) w.add(qr) w.show_all() Gtk.main() if __name__ == '__main__': import sys logging.basicConfig(level=logging.DEBUG) data = sys.argv[1] main(data) gnome-keysign-0.9/keysign/SignPages.py000066400000000000000000000137151310507150200200420ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Andrei Macavei # Copyright 2014, 2015 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from itertools import islice import logging import sys from gi.repository import GObject, Gtk, GLib from datetime import datetime from compat import gtkbutton from scan_barcode import BarcodeReaderGTK, ScalingImage log = logging.getLogger(__name__) # Pages for "Get Key" Tab class ScanFingerprintPage(Gtk.HBox): def __init__(self): super(ScanFingerprintPage, self).__init__() self.set_spacing(10) # set up labels leftLabel = Gtk.Label() leftLabel.set_markup('Type fingerprint') rightLabel = Gtk.Label() rightLabel.set_markup('... or scan QR code') # set up text editor self.textview = Gtk.TextView() self.textbuffer = self.textview.get_buffer() # set up scrolled window scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.add(self.textview) # set up webcam frame scanFrame = Gtk.Frame(label='QR Scanner') scanFrame = BarcodeReaderGTK() scanFrame.set_size_request(150,150) scanFrame.show() self.barcode_scanner = scanFrame # set up load button: this will be used to load a qr code from a file self.loadButton = Gtk.Button('Open Image') self.loadButton.set_image(Gtk.Image.new_from_icon_name('gtk-open', Gtk.IconSize.BUTTON)) self.loadButton.connect('clicked', self.on_loadbutton_clicked) self.loadButton.set_always_show_image(True) # set up left box leftBox = Gtk.VBox(spacing=10) leftBox.pack_start(leftLabel, False, False, 0) leftBox.pack_start(scrolledwindow, True, True, 0) # set up right box rightBox = Gtk.VBox(spacing=10) rightBox.pack_start(rightLabel, False, False, 0) rightBox.pack_start(scanFrame, True, True, 0) rightBox.pack_start(self.loadButton, False, False, 0) # pack up self.pack_start(leftBox, True, True, 0) self.pack_start(rightBox, True, True, 0) def get_text(self): '''Returns the contents of the fingerprint input widget. Note that this function does not format or validate anything. ''' start_iter = self.textbuffer.get_start_iter() end_iter = self.textbuffer.get_end_iter() raw_text = self.textbuffer.get_text(start_iter, end_iter, False) return raw_text def on_loadbutton_clicked(self, *args, **kwargs): print("load") def on_barcode (self, barcode_reader, barcode, gstmessage, pixbuf): self.emit ("barcode_scanned", barcode, gstmessage, pixbuf) class SignKeyPage(Gtk.HBox): def __init__(self, key, image=None): super(SignKeyPage, self).__init__() self.set_spacing(5) self.mainLabel = Gtk.Label() self.mainLabel.set_line_wrap(True) self.pack_start(self.mainLabel, False, True, 0) self.barcode_image = ScalingImage() self.pack_start(self.barcode_image, True, True, 0) self.display_downloaded_key(key, None, image) def display_downloaded_key(self, key, scanned_fpr, image): # FIXME: If the two fingerprints don't match, the button # should be disabled key_text = GLib.markup_escape_text("{}".format(key)) markup = """\ Signing the following key {0} Press 'Next' if you have checked the ID of the person and you want to sign all UIDs on this key.""".format(key_text) self.mainLabel.set_markup(markup) self.mainLabel.show() # The image *can* be None, if the user typed the fingerprint manually, # e.g. did not use Web cam to scan a QR-code if image: self.barcode_image.set_from_pixbuf(image) class PostSignPage(Gtk.VBox): def __init__(self): super(PostSignPage, self).__init__() self.set_spacing(10) # setup the label signedLabel = Gtk.Label() signedLabel.set_text('The key was signed and an email was sent to key owner! What next?') # setup the buttons sendBackButton = Gtk.Button(' Resend email ') sendBackButton.set_image(Gtk.Image.new_from_icon_name("gtk-network", Gtk.IconSize.BUTTON)) sendBackButton.set_always_show_image(True) sendBackButton.set_halign(Gtk.Align.CENTER) saveButton = Gtk.Button(' Save key locally ') saveButton.set_image(Gtk.Image.new_from_icon_name("gtk-save", Gtk.IconSize.BUTTON)) saveButton.set_always_show_image(True) saveButton.set_halign(Gtk.Align.CENTER) emailButton = Gtk.Button('Revoke signature') emailButton.set_image(Gtk.Image.new_from_icon_name("gtk-clear", Gtk.IconSize.BUTTON)) emailButton.set_always_show_image(True) emailButton.set_halign(Gtk.Align.CENTER) # pack them into a container for alignment container = Gtk.VBox(spacing=3) container.pack_start(signedLabel, False, False, 5) container.pack_start(sendBackButton, False, False, 0) container.pack_start(saveButton, False, False, 0) container.pack_start(emailButton, False, False, 0) container.set_valign(Gtk.Align.CENTER) self.pack_start(container, True, False, 0) gnome-keysign-0.9/keysign/__init__.py000066400000000000000000000016231310507150200177140ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from ._version import __version__ def main(*args, **kwargs): from . import app return app.main(*args, **kwargs) gnome-keysign-0.9/keysign/__main__.py000066400000000000000000000020671310507150200177000ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging, sys def main(*args, **kwargs): from . import app return app.main(*args, **kwargs) if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') sys.exit(main()) gnome-keysign-0.9/keysign/_version.py000066400000000000000000000000521310507150200177740ustar00rootroot00000000000000#!/usr/bin/env python __version__ = '0.9' gnome-keysign-0.9/keysign/app.py000066400000000000000000000256231310507150200167430ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2017 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import re import os import signal import sys import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib gi.require_version('Gst', '1.0') from gi.repository import Gst from gi.repository import Gdk if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .avahioffer import AvahiHTTPOffer from .avahidiscovery import AvahiKeysignDiscoveryWithMac from .keyconfirm import PreSignWidget from .keyfprscan import KeyFprScanWidget from .keylistwidget import KeyListWidget from .KeyPresent import KeyPresentWidget from .gpgmh import openpgpkey_from_data from . import gpgmh from .receive import ReceiveApp from .send import SendApp from .util import sign_keydata_and_send from . import gtkexcepthook log = logging.getLogger(__name__) def remove_whitespace(s): cleaned = re.sub('[\s+]', '', s) return cleaned class PswMappingReceiveApp(ReceiveApp): """A simple extension to the existing Receive class to connect to the PreSignWidget's mapped signal (or an emulation thereof. This is a bit of a hack, but by having pushed common receive functionality in the ReceiveApp class, we do not necessarily control anymore when the PreSignWidget is created let alone connect to the map signal in time. """ def __init__(self, mapped_func, builder=None): # ReceiveApp, in Python 2, is an old style object ReceiveApp.__init__(self, builder=builder) self.func = mapped_func def on_keydata_downloaded(self, *args, **kwargs): ReceiveApp.on_keydata_downloaded(self, *args, **kwargs) psw = self.psw psw.connect('map', self.func) if psw.get_mapped(): self.func(psw) class KeysignApp(Gtk.Application): def __init__(self, *args, **kwargs): super(KeysignApp, self).__init__(*args, **kwargs) self.connect('activate', self.on_activate) self.send_stack = None self.receive_stack = None self.send_receive_stack = None self.header_button_handler_id = None self.pre_sign_widget = None def on_activate(self, app): ui_file_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "app.ui") appwindow = 'applicationwindow1' builder = Gtk.Builder() builder.add_objects_from_file(ui_file_path, [appwindow]) window = builder.get_object(appwindow) window.set_wmclass ("GNOME Keysign", "GNOME Keysign") window.set_title("GNOME Keysign") self.headerbar = window.get_titlebar() self.header_button = builder.get_object("back_refresh_button") self.header_button.connect('clicked', self.on_header_button_clicked) sw = builder.get_object('stackswitcher1') # FIXME: I want to be able to press Alt+S and Alt+R respectively # to switch the stack pages to Send and Receive. # It's possible when using the Gtk Inspector and modify the # Switcher's children (ToggleButton and Label) to "use-underscore". # but it must be possible to do programmatically. # sw.get_children() self.stack_switcher = sw self.send_receive_stack = builder.get_object("send_receive_stack") self.send_receive_stack.connect('notify::visible-child', self.on_sr_stack_switch) ## Load Send part self.send = SendApp() ss = self.send.stack p = ss.get_parent() if p: p.remove(ss) ss.connect('notify::visible-child', self.on_send_stack_switch) ss.connect('map', self.on_send_stack_mapped) klw = self.send.klw klw.connect("key-activated", self.on_key_activated) klw.connect("map", self.on_keylist_mapped) klw.props.margin_left = klw.props.margin_right = 15 self.send_stack = ss ## End of loading send part # Load Receive part self.receive = PswMappingReceiveApp(self.on_presign_mapped) rs = self.receive.stack rs.connect('notify::visible-child', self.on_receive_stack_switch) scanner = self.receive.scanner scanner.connect("map", self.on_scanner_mapped) self.receive_stack = rs self.send_receive_stack.add_titled(self.send_stack, "send_stack", "Send") self.send_receive_stack.add_titled(rs, "receive_stack", "Receive") # These properties must be set after the stacks has been added to the window # because they require a window element that "receive.ui" file doesn't provide. accel_group = Gtk.AccelGroup() window.add_accel_group(accel_group) self.receive.accept_button.add_accelerator("clicked", accel_group, ord('o'), Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.VISIBLE) self.receive.accept_button.set_can_default(True) window.show_all() self.add_window(window) def run(self, args=[]): super(KeysignApp, self).run() def on_key_activated(self, widget, key): log.info("Activated key %r", key) # Ouf, we rely on the the SendApp to have reacted to # the signal first, so that it sets up the keypresentwidget # and so that we can access it here. If it did, however, # We might not be able to catch the mapped signal quickly # enough. So we ask the widget wether it is already mapped. kpw = self.send.kpw kpw.connect('map', self.on_keypresent_mapped) log.debug("KPW to wait for map: %r (%r)", kpw, kpw.get_mapped()) if kpw.get_mapped(): # The widget is already visible. Let's quickly call our handler self.on_keypresent_mapped(kpw) #### # Saving subtitle self.headerbar_subtitle = self.headerbar.get_subtitle() self.headerbar.set_subtitle("Sending {}".format(key.fpr)) #### # Making button clickable self.header_button.set_sensitive(True) def on_sr_stack_switch(self, stack, *args): log.debug("Switched Stack! %r", args) #self.update_header_button() def on_send_stack_switch(self, stack, *args): log.debug("Switched Send Stack! %r", args) #self.update_header_button() def on_receive_stack_switch(self, stack, *args): log.debug("Switched Receive Stack! %r", args) #self.update_header_button() def on_send_header_button_clicked(self, button, *args): # Here we assume that there is only one place where # we could have possibly pressed this button, i.e. # from the keypresentwidget. log.debug("Send Headerbutton %r clicked! %r", button, args) klw = self.send.klw self.send_stack.set_visible_child(klw) self.send.deactivate() def on_receive_header_button_clicked(self, button, *args): # Here we assume that there is only one place where # we could have possibly pressed this button, i.e. # from the presignwidget. log.debug("Receive Headerbutton %r clicked! %r", button, args) self.receive_stack.set_visible_child_name("scanner") def on_header_button_clicked(self, button, *args): log.debug("Headerbutton %r clicked! %r", button, args) # We have 2 children in the top level stack: send and receive. # In the send stack, we currently have two children. # In the receive stack, we have at least three. visible_child = self.send_receive_stack.get_visible_child() if not visible_child: return if visible_child == self.send_stack: return self.on_send_header_button_clicked(button, *args) elif visible_child == self.receive_stack: return self.on_receive_header_button_clicked(button, *args) else: raise RuntimeError("We expected either send or receive stack " "but got %r" % visible_child) def on_keylist_mapped(self, keylistwidget): log.debug("Keylist becomes visible!") self.header_button.set_image( Gtk.Image.new_from_icon_name("view-refresh", Gtk.IconSize.BUTTON)) # We don't support refreshing for now. self.header_button.set_sensitive(False) def on_send_stack_mapped(self, stack): log.debug("send stack becomes visible!") def on_keypresent_mapped(self, kpw): log.debug("keypresent becomes visible!") self.header_button.set_sensitive(True) self.header_button.set_image( Gtk.Image.new_from_icon_name("go-previous", Gtk.IconSize.BUTTON)) def on_scanner_mapped(self, scanner): log.debug("scanner becomes visible!") self.header_button.set_sensitive(False) self.header_button.set_image( Gtk.Image.new_from_icon_name("go-previous", Gtk.IconSize.BUTTON)) def on_presign_mapped(self, psw): log.debug("presign becomes visible!") self.header_button.set_sensitive(True) self.header_button.set_image( Gtk.Image.new_from_icon_name("go-previous", Gtk.IconSize.BUTTON)) def main(args = []): logging.basicConfig( level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') log = logging.getLogger(__name__) log.debug('Running main with args: %s', args) if not args: args = [] Gst.init() app = KeysignApp() try: GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: pass app.run(args) if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') sys.exit(main(sys.argv[1:])) gnome-keysign-0.9/keysign/app.ui000066400000000000000000000045651310507150200167320ustar00rootroot00000000000000 False 6 center 600 400 False True False crossfade True False False True True True True True True False gtk-refresh True False send_receive_stack gnome-keysign-0.9/keysign/avahidiscovery.py000066400000000000000000000173111310507150200211760ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import os import sys from requests.exceptions import ConnectionError from gi.repository import GObject, GLib if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .util import strip_fingerprint, download_key_http, parse_barcode try: from .gpgmh import fingerprint_from_keydata except ImportError: # FIXME: Remove this conditional from .gpgmh import fingerprint_for_key as fingerprint_from_keydata from .network.AvahiBrowser import AvahiBrowser from .util import mac_verify log = logging.getLogger(__name__) class AvahiKeysignDiscovery(GObject.GObject): "A client discovery using Avahi" __gsignals__ = { # Gets emitted whenever a new server has been found or has been removed. # Is also emitted shortly after an object has been created. str("list-changed"): (GObject.SIGNAL_RUN_LAST, None, (int,)), } def __init__(self, *args, **kwargs): super(AvahiKeysignDiscovery, self).__init__(*args, **kwargs) self.log = logging.getLogger(__name__) # We should probably try to put this constant in a more central place avahi_service_type = '_gnome-keysign._tcp' self.avahi_browser = AvahiBrowser(service=avahi_service_type) self.avahi_browser.connect('new_service', self.on_new_service) self.avahi_browser.connect('remove_service', self.on_remove_service) self.discovered_services = [] # It seems we cannot emit directly... GLib.idle_add(lambda: self.emit("list-changed", len(self.discovered_services))) def on_new_service(self, browser, name, address, port, txt_dict): published_fpr = txt_dict.get('fingerprint', None) self.log.info("discovered something: %s %s:%i:%s", name, address, port, published_fpr) if not address.startswith('fe80::'): # We intend to ignore IPv6 link local addresses, because it seems # that you cannot just connect to that address without also # knowing which NIC the address belongs to. # http://serverfault.com/a/794967 # FIXME: Use something more sane like attr.s instead of the tuple self.discovered_services += ((name, address, port, published_fpr), ) self.emit("list-changed", len(self.discovered_services)) def on_remove_service(self, browser, service_type, name): '''Handler for the on_remove signal from AvahiBrowser Removes a service from the internal list by calling remove_discovered_service. ''' self.log.info("Received a remove signal, let's check; %s:%s", service_type, name) self.remove_discovered_service(name) def remove_discovered_service(self, name): '''Removes server-side clients from discovered_services list when the server name with fpr is a match.''' for client in self.discovered_services: if client[0] == name: self.discovered_services.remove(client) self.emit("list-changed", len(self.discovered_services)) self.log.info("Clients currently in list '%s'", self.discovered_services) def find_key(self, userdata): "Returns the key if it thinks it found one..." self.log.info("Trying to find key with %r", userdata) parsed = parse_barcode(userdata) cleaned = strip_fingerprint(parsed["fingerprint"]) downloaded_key = None # FIXME: Replace with attr.ib for (name, address, port, fpr) in self.discovered_services: if cleaned == fpr: # This is blocking :-/ try: downloaded_key = download_key_http(address, port) if fingerprint_from_keydata(downloaded_key) != cleaned: continue except ConnectionError: self.log.exception("Error downloading from %r:%r", address, port) return downloaded_key class AvahiKeysignDiscoveryWithMac(AvahiKeysignDiscovery): def find_key(self, userdata): "Returns the key if it thinks it found one which also matched the MAC" key = super(AvahiKeysignDiscoveryWithMac, self).find_key(userdata) if key: # For now, we cannot assume that a MAC exists, simply because # currently the MAC is only transferred via the barcode. # The user, however, might as well enter the fingerprint # manually. Unless we stop allowing that, we won't have a MAC. mac = parse_barcode(userdata).get("MAC", [None])[0] if mac is None: # This is the ugly shortcut which exists for legacy reasons verified_key = key else: mac_key = fingerprint_from_keydata(key) verified = mac_verify(mac_key.encode('ascii'), key, mac) if verified: verified_key = key else: self.log.info("MAC validation failed: %r", verified) verified_key = None else: verified_key = None return verified_key def main(args): log = logging.getLogger(__name__) log.debug('Running main with args: %s', args) if not args: raise ValueError("You must provide an argument to identify the key") loop = GObject.MainLoop() arg = args[0] # FIXME: Enable parameter timeout = 5 GObject.timeout_add_seconds(timeout, lambda: loop.quit()) discover = AvahiKeysignDiscovery() # We quickly attach the found to the object to maintain state discover.found_key = None def find_key(): keydata = discover.find_key(arg) if keydata: log.info("Found %d key bytes", len(keydata)) discover.found_key = keydata print (keydata) loop.quit() return not keydata discover.avahi_browser.connect('new_service', lambda *args: find_key()) # Instead of using this implementation detail for getting the notification, # it would be possible to repeatedly call find_key. # GObject.timeout_add(25, lambda: find_key() and False) # GObject.timeout_add(500, find_key) loop.run() if not discover.found_key: log.error("No Key found for %r!!1", arg) if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') sys.exit(main(sys.argv[1:])) gnome-keysign-0.9/keysign/avahioffer.py000066400000000000000000000056701310507150200202750ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import signal import sys import argparse import logging import os from gi.repository import Gtk, GLib from gi.repository import GObject if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .__init__ import __version__ from .gpgmh import get_usable_keys, get_public_key_data from .util import mac_generate from . import Keyserver log = logging.getLogger(__name__) class AvahiHTTPOffer: "Spawns a local HTTP daemon and announces it via Avahi" def __init__(self, key): self.key = key self.fingerprint = fingerprint = key.fingerprint self.keydata = keydata = get_public_key_data(fingerprint) self.keyserver = Keyserver.ServeKeyThread(str(keydata), fingerprint) self.mac = mac = mac_generate(fingerprint, keydata) def start(self): "Starts offering the key" fingerprint = self.fingerprint.upper() mac = self.mac.upper() discovery_info = 'OPENPGP4FPR:{0}#MAC={1}'.format( fingerprint, mac) log.info("Requesting to start") self.keyserver.start() return discovery_info def stop(self): "Stops offering the key" log.info("Requesting to shutdown") self.keyserver.shutdown() def main(args): if not args: raise ValueError("You must provide an argument to identify the key") key = get_usable_keys(pattern=args[0])[0] offer = AvahiHTTPOffer(key) discovery_info = offer.start() print ("Offering key: {}".format(key)) print ("Discovery info: {}".format(discovery_info)) print ("Press Enter to stop") raw_input() offer.stop() if __name__ == "__main__": import sys main(sys.argv[1:]) gnome-keysign-0.9/keysign/compat/000077500000000000000000000000001310507150200170645ustar00rootroot00000000000000gnome-keysign-0.9/keysign/compat/__init__.py000066400000000000000000000000001310507150200211630ustar00rootroot00000000000000gnome-keysign-0.9/keysign/compat/gtkbutton.py000066400000000000000000000020651310507150200214620ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2015 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . """This is a simple compatibility layer for the Gtk.Button and its set_always_show_image method which exists from Gtk 3.6 only. """ from gi.repository import Gtk if not hasattr(Gtk.Button, 'set_always_show_image'): setattr(Gtk.Button, 'set_always_show_image', lambda x,y: None) gnome-keysign-0.9/keysign/gnome-keysign-sign-key.py000077500000000000000000000031701310507150200224570ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2015 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import sys from time import sleep from .util import sign_keydata_and_send def main(args): log = logging.getLogger(__name__) log.debug('Running main with args: %s', args) if not args: raise ValueError("You need to give filesnames as args: %s" % args) for fname in args: data = open(fname, 'r').read() log.info("Calling %r to sign %s", sign_keydata_and_send, fname) tmpfiles = list(sign_keydata_and_send(keydata=data)) log.info("Finished signing. Feel free to Quit the application. " + "We're only waiting for a few seconds for the signature " + "files to be picked up") sleep(3) if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') sys.exit(main(sys.argv[1:])) gnome-keysign-0.9/keysign/gpgkey.py000066400000000000000000000124171310507150200174460ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from __future__ import unicode_literals from collections import namedtuple from datetime import datetime import logging import warnings log = logging.getLogger(__name__) def parse_uid(uid): "Parses a GnuPG UID into it's name, comment, and email component" # remove the comment from UID (if it exists) com_start = uid.find(b'(') if com_start != -1: com_end = uid.find(b')') uid = uid[:com_start].strip() + uid[com_end+1:].strip() # FIXME: Actually parse the comment... comment = "" # split into user's name and email tokens = uid.split(b'<') name = tokens[0].strip() email = 'unknown' if len(tokens) > 1: email = tokens[1].replace('>','').strip() log.debug("Parsed %r to name (%d): %r", uid, len(name), name) return (name, comment, email) def parse_expiry(value): """Takes either a string, an epoch, or a datetime and converts it to a datetime. If the string is empty (or otherwise evaluates to False) then this function returns None, meaning that no expiry has been set. An edge case is the epoch value "0". """ if not value: expiry = None else: try: expiry = datetime.fromtimestamp(int(value)) except TypeError: expiry = value return expiry class Key(namedtuple("Key", ["expiry", "fingerprint", "uidslist"])): "Represents an OpenPGP Key to extent we care about" log = logging.getLogger(__name__) def __new__(cls, expiry, fingerprint, uidslist, *args, **kwargs): exp_date = parse_expiry(expiry) self = super(Key, cls).__new__(cls, exp_date, fingerprint, uidslist) return self def __format__(self, arg): s = "{fingerprint}\r\n" s += '\r\n'.join((" {}".format(uid) for uid in self.uidslist)) # This is what original output looks like: # pub [unknown] 3072R/1BF98D6D 1336669781 [expiry: 2017-05-09 19:09:41] # Fingerprint = FF52 DA33 C025 B1E0 B910 92FC 1C34 19BF 1BF9 8D6D # uid 1 [unknown] Tobias Mueller # uid 2 [unknown] Tobias Mueller <4tmuelle@informatik.uni-hamburg.de> # sub 3072R/3B76E8B3 1336669781 [expiry: 2017-05-09 19:09:41] return s.format(**self._asdict()) @property def fpr(self): "Legacy compatibility, use fingerprint instead" warnings.warn("Legacy fpr, use the fingerprint property", DeprecationWarning) return self.fingerprint @classmethod def from_monkeysign(cls, key): "Creates a new Key from an existing monkeysign key" log.debug("From mks: %r", key) uids = [UID.from_monkeysign(uid) for uid in key.uidslist] expiry = parse_expiry(key.expiry) fingerprint = key.fpr return cls(expiry, fingerprint, uids) @classmethod def from_gpgme(cls, key): "Creates a new Key from an existing monkeysign key" uids = [UID.from_gpgme(uid) for uid in key.uids] expiry = parse_expiry(key.subkeys[0].expires) fingerprint = key.fpr return cls(expiry, fingerprint, uids) class UID(namedtuple("UID", "expiry name comment email")): "Represents an OpenPGP UID - at least to the extent we care about it" @classmethod def from_monkeysign(cls, uid): "Creates a new UID from a monkeysign key" # We expect to get raw bytes. # While RFC4880 demands UTF-8 encoded data, # real-life has produced non UTF-8 keys... uidstr = uid.uid log.debug("UidStr (%d): %r", len(uidstr), uidstr) name, comment, email = parse_uid(uidstr) expiry = parse_expiry(uid.expire) return cls(expiry, name, comment, email) @classmethod def from_gpgme(cls, uid): "Creates a new UID from a monkeysign key" uidstr = uid.uid name = uid.name comment = '' # FIXME: uid.comment email = uid.email expiry = None # FIXME: Maybe UIDs don't expire themselves but via the binding signature return cls(expiry, name, comment, email) def __format__(self, arg): if self.comment: s = b"{name} ({comment}) <{email}>" else: s = b"{name} <{email}>" return s.format(**self._asdict()) def __str__(self): return b"{}".format(self) @property def uid(self): "Legacy compatibility, use str() instead" warnings.warn("Legacy uid, use '{}'.format() instead", DeprecationWarning) return b"{}".format(self) gnome-keysign-0.9/keysign/gpgmeh.py000066400000000000000000000342361310507150200174320ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from __future__ import unicode_literals import logging import os # The SigningKeyring uses os.symlink for the agent import sys from tempfile import mkdtemp import gpg from gpg.constants import PROTOCOL_OpenPGP from .gpgkey import Key, UID texttype = unicode if sys.version_info.major < 3 else str log = logging.getLogger(__name__) ##### ## INTERNAL API ## class GenEdit: _ignored_status = (gpg.constants.STATUS_EOF, gpg.constants.STATUS_GOT_IT, gpg.constants.STATUS_NEED_PASSPHRASE, gpg.constants.STATUS_GOOD_PASSPHRASE, gpg.constants.STATUS_BAD_PASSPHRASE, gpg.constants.STATUS_USERID_HINT, gpg.constants.STATUS_SIGEXPIRED, gpg.constants.STATUS_KEYEXPIRED, gpg.constants.STATUS_PROGRESS, gpg.constants.STATUS_KEY_CREATED, gpg.constants.STATUS_ALREADY_SIGNED) def __init__(self, generator): generator.send(None) self.generator = generator self.last_sink_index = 0 def edit_cb(self, status, args, sink=None): if status in self._ignored_status: logging.info("Returning None for %r %r", status, args) return if not status: logging.info("Closing for %r", status) self.generator.close() return # 0 is os.SEEK_SET if sink: # os.SEEK_CUR = 1 current = sink.seek(0, 1) sink.seek(self.last_sink_index, 0) sinkdata = sink.read(current) self.last_sink_index = current else: sinkdata = None log.info("edit_cb: %r %r '%s'", status, args, sinkdata) data = self.generator.send((status, args)) #, sinkdata)) log.info("edit_cb data: %r", data) return texttype(data) def del_uids(uids): status, arg = yield None log.info("status args: %r %r", status, arg) #log.info("sinkdata: %s", sinkdata) #uids = [l for l in sinkdata.splitlines() if l.startswith('uid:')] #log.info("UIDs: %s", uids) status, arg = yield "list" log.info("status args: %r %r", status, arg) for uid in uids: status, arg = yield "uid %d" % uid log.info("status args: %r %r", status, arg) if uids: status, arg = yield "deluid" log.info("status args: %r %r", status, arg) assert status == gpg.constants.STATUS_GET_BOOL, "%r %r" % (status, arg) assert arg == 'keyedit.remove.uid.okay' status, arg = yield "Y" log.info("status args: %r %r", status, arg) yield 'save' def sign_key(uid=0, sign_cmd=u"sign", expire=False, check=3, error_cb=None): status, prompt = yield None assert status == gpg.constants.STATUS_GET_LINE assert prompt == u"keyedit.prompt" status, prompt = yield u"uid %d" % uid # We ignore GOT_IT... # assert status == gpg.constants.STATUS_GOT_IT #status, prompt = yield None assert status == gpg.constants.STATUS_GET_LINE status, prompt = yield sign_cmd # We ignore GOT_IT... # assert status == gpg.constants.STATUS_GOT_IT while prompt != 'keyedit.prompt': if prompt == 'keyedit.sign_all.okay': status, prompt = yield 'Y' elif prompt == 'sign_uid.expire': status, prompt = yield '%s' % ('Y' if expire else 'N') elif prompt == 'sign_uid.class': status, prompt = yield '%d' % check elif prompt == 'sign_uid.okay': status, prompt = yield 'Y' elif status == gpg.constants.STATUS_INV_SGNR: # When does this actually happen? status, prompt = yield None elif status == gpg.constants.STATUS_PINENTRY_LAUNCHED: status, prompt = yield None elif status == gpg.constants.STATUS_GOT_IT: status, prompt = yield None elif status == gpg.constants.STATUS_ALREADY_SIGNED: status, prompt = yield u'Y' elif status == gpg.constants.STATUS_ERROR: if error_cb: error_cb(prompt) else: raise RuntimeError("Error signing key: %s" % prompt) status, prompt = yield None else: raise AssertionError("Unexpected state %r %r" % (status, prompt)) yield u"save" def UIDExport(keydata, uid_i): """Export only the UID of a key. Unfortunately, GnuPG does not provide smth like --export-uid-only in order to obtain a UID and its signatures.""" log.debug("Deletion of UID %r from %r", uid_i, keydata) if not uid_i >= 1: log.debug("Raising because uid: %r", uid_i) raise ValueError("Expected UID to be >= 1, but is %r", uid_i) ctx = TempContext() ctx.op_import(keydata) result = ctx.op_import_result() if result.considered != 1 or result.imported != 1: raise ValueError("Expected exactly one key in keydata. %r" % result) else: assert len(result.imports) == 1 fpr = result.imports[0].fpr key = ctx.get_key(fpr) uids_to_remove = {i for i in range(1, len(key.uids)+1)} uids_to_remove.remove(uid_i) if uids_to_remove: sink = gpg.Data() ctx.interact(key, GenEdit(del_uids(uids_to_remove)).edit_cb, fnc_value=sink, sink=sink) sink.seek(0, 0) log.debug("Data after UIDExport: %s", sink.read()) uid_data = gpg.Data() ctx.op_export_keys([key], 0, uid_data) uid_data.seek(0, 0) uid_bytes = uid_data.read() log.debug("UID %r: %r", uid_i, uid_bytes) return uid_bytes def export_uids(keydata): """Export each valid and non-revoked UID of a key""" ctx = TempContext() ctx.op_import(keydata) result = ctx.op_import_result() log.debug("ExportUIDs: Imported %r", result) if result.considered != 1 or result.imported != 1: raise ValueError("Expected exactly one key in keydata. %r" % result) else: assert len(result.imports) == 1 fpr = result.imports[0].fpr key = ctx.get_key(fpr) for i, uid in enumerate(key.uids, start=1): log.info("Potentially deleting UID %d: %r", i, uid) if not uid.invalid and not uid.revoked: uid_data = UIDExport(keydata, i) yield (uid.uid, uid_data) def is_usable(key): unusable = key.invalid or key.disabled \ or key.expired or key.revoked log.debug('Key %s is invalid: %s (i:%s, d:%s, e:%s, r:%s)', key, unusable, key.invalid, key.disabled, key.expired, key.revoked) return not unusable def filter_usable_keys(keys): usable_keys = [Key.from_gpgme(key) for key in keys if is_usable(key)] log.debug('Identified usable keys: %s', usable_keys) return usable_keys class DirectoryContext(gpg.Context): def __init__(self, homedir): super(DirectoryContext, self).__init__() self.set_engine_info(PROTOCOL_OpenPGP, None, homedir) self.homedir = homedir class TempContext(DirectoryContext): def __init__(self): self.homedir = mkdtemp() super(TempContext, self).__init__(homedir=self.homedir) def __del__(self): try: # shutil.rmtree(self.homedir, ignore_errors=True) pass except: log.exception("During cleanup of %r", self.homedir) class TempContextWithAgent(TempContext): def __init__(self, oldctx): super(TempContextWithAgent, self).__init__() homedir = self.homedir if oldctx: old_homedir = oldctx.engine_info.home_dir if not old_homedir: old_homedir = os.path.join(os.path.expanduser("~"), ".gnupg") else: old_homedir = os.path.join(os.path.expanduser("~"), ".gnupg") log.info("Old homedir: %r", old_homedir) old_agent_path = os.path.expanduser(os.path.join(old_homedir, "S.gpg-agent")) new_agent_path = os.path.expanduser(os.path.join(homedir, "S.gpg-agent")) os.symlink(old_agent_path, new_agent_path) assert len(list(self.keylist())) == 0 secret_keys = list(oldctx.keylist(secret=True)) for key in secret_keys: def export_key(fpr): # FIXME: The Context should really be able to export() public_key = gpg.Data() oldctx.op_export(fpr, 0, public_key) public_key.seek(0, os.SEEK_SET) return public_key keydata = export_key(key.subkeys[0].fpr) self.op_import(keydata) # FIXME: I guess we should assert on the result assert len(list(self.keylist())) == len(secret_keys) ## ## END OF INTERNAL API ##### def openpgpkey_from_data(keydata): c = TempContext() c.op_import(gpg.Data(keydata)) result = c.op_import_result() log.debug("Import Result: %s", result) if result.imported != 1: raise ValueError("Keydata did not contain exactly one key, but %r" % result.imported) else: imported = result.imports import_ = imported[0] fpr = import_.fpr key = c.get_key(fpr) return Key.from_gpgme(key) def get_public_key_data(fpr, homedir=None): c = DirectoryContext(homedir) c.armor = True sink = gpg.Data() # FIXME: There will probably be an export() function c.op_export(fpr, 0, sink) sink.seek(0, os.SEEK_SET) keydata = sink.read() log.debug("Exported %r: %r", fpr, keydata) if not keydata: s = "No data to export for {} (in {})".format(fpr, homedir) raise ValueError(s) return keydata def fingerprint_from_keydata(keydata): '''Returns the OpenPGP Fingerprint for a given key''' openpgpkey = openpgpkey_from_data(keydata) return openpgpkey.fpr def get_usable_keys_from_context(ctx, pattern="", secret=False): keys = [Key.from_gpgme(key) for key in ctx.keylist(pattern=pattern, secret=secret) if is_usable(key)] return keys def get_usable_keys(pattern="", homedir=None): '''Uses get_keys on the keyring and filters for non revoked, expired, disabled, or invalid keys''' log.debug('Retrieving keys for %s, %s', pattern, homedir) ctx = DirectoryContext(homedir=homedir) return get_usable_keys_from_context(ctx, pattern=pattern, secret=False) def get_usable_secret_keys(pattern="", homedir=None): '''Returns all secret keys which can be used to sign a key''' ctx = DirectoryContext(homedir=homedir) return get_usable_keys_from_context(ctx, pattern=pattern, secret=True) def minimise_key(keydata): "Returns the public key exported under the MINIMAL mode" ctx = TempContext() ctx.op_import(keydata) result = ctx.op_import_result() if result.considered != 1 and result.imported != 1: raise ValueError("Expected to load exactly one key. %r", result) else: imports = [i for i in result.imports if i.status == gpg.constants.IMPORT_NEW] log.debug("Import %r", result) assert len(imports) == 1 fpr = result.imports[0].fpr key = ctx.get_key(fpr) sink = gpg.Data() ctx.op_export_keys([key], gpg.constants.EXPORT_MODE_MINIMAL, sink) sink.seek(0, 0) minimised_key = sink.read() return minimised_key def sign_keydata_and_encrypt(keydata, error_cb=None, homedir=None): oldctx = DirectoryContext(homedir) ctx = TempContextWithAgent(oldctx) # We're trying to sign with all available secret keys available_secret_keys = [key for key in ctx.keylist(secret=True) if not key.disabled or key.revoked or key.invalid or key.expired] ctx.signers = available_secret_keys ctx.op_import(minimise_key(keydata)) result = ctx.op_import_result() if result.considered != 1 and result.imported != 1: raise ValueError("Expected to load exactly one key. %r", result) else: imports = result.imports assert len(imports) == 1 fpr = result.imports[0].fpr key = ctx.get_key(fpr) sink = gpg.Data() # There is op_keysign, but it's only available with gpg 2.1.12 ctx.interact(key, GenEdit(sign_key(error_cb=error_cb)).edit_cb, sink=sink) sink.seek(0, 0) log.debug("Sink after signing: %s", sink.read()) signed_sink = gpg.Data() ctx.set_keylist_mode(gpg.constants.KEYLIST_MODE_SIGS) ctx.armor = True ctx.op_export_keys([key], 0, signed_sink) signed_sink.seek(0, 0) signed_keydata = signed_sink.read() log.debug("Signed Key: %s", signed_keydata) # Do I have to re-get the key to make the signatures known? key = ctx.get_key(fpr) for i, uid in enumerate(key.uids, start=1): if uid.revoked or uid.invalid: continue else: uid_data = UIDExport(signed_keydata, i) log.debug("Data for uid %d: %r, sigs: %r %r", i, uid, uid.signatures, uid_data) ciphertext, _, _ = ctx.encrypt(plaintext=uid_data, recipients=[key], # We probably have to set owner trust # in order for it to work out of the box always_trust=True, sign=False) yield (UID.from_gpgme(uid), ciphertext) gnome-keysign-0.9/keysign/gpgmh.py000066400000000000000000000035411310507150200172600ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import os # The SigningKeyring uses os.symlink for the agent # The UID object is used in one place, at least, # to get display the name and email address. # The Key object is returned from a few functions, so it's # API is somewhat external. from .gpgkey import Key, UID log = logging.getLogger(__name__) # We allow for disabling the gpgme based library for now, # because it may turn out to be not working as well as expected. # We also use the standard monkeysign module for now, because # we know it better. Expect that to change, though. try: GPGME = int(os.environ.get("KEYSIGN_GPGME", 0)) if GPGME: from . import gpgmeh as gpg else: from . import gpgmks as gpg except ImportError: from . import gpgmks as gpg # We expect these functions: get_usable_keys = gpg.get_usable_keys openpgpkey_from_data = gpg.openpgpkey_from_data get_public_key_data = gpg.get_public_key_data fingerprint_from_keydata = gpg.fingerprint_from_keydata get_usable_secret_keys = gpg.get_usable_secret_keys sign_keydata_and_encrypt = gpg.sign_keydata_and_encrypt gnome-keysign-0.9/keysign/gpgmks.py000066400000000000000000000363271310507150200174560ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2017 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from datetime import datetime import logging import os # The SigningKeyring uses os.symlink for the agent from tempfile import NamedTemporaryFile # The UID object is used in one place, at least, # to get display the name and email address. # The Key object is returned from a few functions, so it's # API is somewhat external. from .gpgkey import Key, UID log = logging.getLogger(__name__) ##### ## INTERNAL API ## import sys parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(os.path.join(parent_dir, "monkeysign")) from monkeysign.gpg import Keyring, TempKeyring from monkeysign.gpg import GpgRuntimeError def UIDExport(uid, keydata): """Export only the UID of a key. Unfortunately, GnuPG does not provide smth like --export-uid-only in order to obtain a UID and its signatures.""" log = logging.getLogger(__name__ + ".UIDExport") tmp = TempKeyring() # Hm, apparently this needs to be set, otherwise gnupg will issue # a stray "gpg: checking the trustdb" which confuses the gnupg library tmp.context.set_option('always-trust') tmp.import_data(keydata) log.debug("Looking for %r", uid) for fpr, key in tmp.get_keys(uid).items(): for u in key.uidslist: key_uid = u.uid if key_uid != uid: log.info('Deleting UID %s from key %s', key_uid, fpr) tmp.del_uid(fingerprint=fpr, pattern=key_uid) only_uid = tmp.export_data(uid) return only_uid def MinimalExport(keydata): '''Returns the minimised version of a key For now, you must provide one key only.''' tmpkeyring = TempKeyring() ret = tmpkeyring.import_data(keydata) log.debug("Returned %s after importing %r", ret, keydata) assert ret tmpkeyring.context.set_option('export-options', 'export-minimal') keys_dict = tmpkeyring.get_keys() # We assume the keydata to contain one key only keys = list(keys_dict.items()) log.debug("Keys after importing: %s (%s)", keys, keys) fingerprint, key = keys[0] stripped_key = tmpkeyring.export_data(fingerprint) return stripped_key class SplitKeyring(Keyring): def __init__(self, primary_keyring_fname, trustdb_fname, *args, **kwargs): # I don't think Keyring is inheriting from object, # so we can't use super() Keyring.__init__(self, *args, **kwargs) self.context.set_option('primary-keyring', primary_keyring_fname) self.context.set_option('trustdb-name', trustdb_fname) self.context.set_option('no-default-keyring') class TempSplitKeyring(SplitKeyring): """A temporary keyring which will be discarded after use It creates a temporary file which will be used for a SplitKeyring. You may not necessarily be able to use this Keyring as is, because gpg1.4 does not like using secret keys which is does not have the public keys of in its pubkeyring. So you may not necessarily be able to perform operations with the user's secret keys (like creating signatures). """ def __init__(self, *args, **kwargs): # A NamedTemporaryFile deletes the backing file self.kr_tempfile = NamedTemporaryFile(prefix='gpgpy-') self.kr_fname = self.kr_tempfile.name self.tdb_tempfile = NamedTemporaryFile(prefix='gpgpy-tdb-', delete=True) self.tdb_fname = self.tdb_tempfile.name # This should delete the file. # Why are we doing it? Well... # Turns out that if you run gpg --trustdb-name with an # empty file, it complains about an invalid trustdb. # If, however, you give it a non-existent filename, # it'll happily create a new trustdb. # FWIW: Am empty trustdb file seems to be 40 bytes long, # but the contents seems to be non-deterministic. # Anyway, we'll leak the file :-/ self.tdb_tempfile.close() SplitKeyring.__init__(self, primary_keyring_fname=self.kr_fname, trustdb_fname=self.tdb_fname, *args, **kwargs) class TempSigningKeyring(TempSplitKeyring): """A temporary keyring which uses the secret keys of a parent keyring Creates a temporary keyring which can use the orignal keyring's secret keys. If you don't provide a keyring as argument (i.e. None), a default Keyring() will be taken which represents the user's regular keyring. In fact, this is not much different from a TempSplitKeyring, but gpg1.4 does not see the public keys for the secret keys when run with --no-default-keyring and --primary-keyring. So we copy the public parts of the secret keys into the primary keyring. """ def __init__(self, base_keyring=None, *args, **kwargs): # Not a new style class... if issubclass(self.__class__, object): super(TempSigningKeyring, self).__init__(*args, **kwargs) else: TempSplitKeyring.__init__(self, *args, **kwargs) if base_keyring is None: base_keyring = Keyring() # Copy the public parts of the secret keys to the tmpkeyring for fpr, key in base_keyring.get_keys(None, secret=True, public=False).items(): self.import_data (base_keyring.export_data (fpr)) ## We don't copy the config file, because we're not using a separate ## homedir. So we expect gpg to still use it's normal homedir and thus ## it's normal configuration. # self.copy_agent_socket(base_keyring) def copy_agent_socket(self, base_keyring): ## Copied from monkeysign/ui.py as of ## 741dde1cc242bf125dd206a019028736d9c4a141 # install the gpg agent socket for GnuPG 2.1 because # --secret-keyring silently fails # this is apparently how we should do things: # https://lists.gnupg.org/pipermail/gnupg-devel/2015-January/029301.html # cargo-culted from caff, thanks guilhem! src = base_keyring.get_agent_socket() dst = self.get_agent_socket() log.info(_('installing symlinks for sockets from %s to %s'), src, dst) try: os.unlink(dst) except OSError as e: if e.errno == errno.ENOENT: pass else: raise os.symlink(src, dst) from monkeysign.gpg import Keyring def parse_sig_list(text): '''Parses GnuPG's signature list (i.e. list-sigs) The format is described in the GnuPG man page''' sigslist = [] for block in text.split("\n"): if block.startswith("sig"): record = block.split(":") log.debug("sig record (%d) %s", len(record), record) keyid, timestamp, uid = record[4], record[5], record[9] sigslist.append((keyid, timestamp, uid)) return sigslist def signatures_for_keyid(keyid, keyring=None): '''Returns the list of signatures for a given key id This will call out to GnuPG list-sigs, using Monkeysign, and parse the resulting string into a list of signatures. A default Keyring will be used unless you pass an instance as keyring argument. ''' if keyring is None: kr = Keyring() else: kr = keyring # FIXME: this would be better if it was done in monkeysign kr.context.call_command(['list-sigs', keyid]) siglist = parse_sig_list(kr.context.stdout) return siglist ## Monkeypatching to get more debug output import monkeysign.gpg bc = monkeysign.gpg.Context.build_command def build_command(*args, **kwargs): ret = bc(*args, **kwargs) #log.info("Building command %s", ret) log.debug("Building cmd: %s", ' '.join(["'%s'" % c for c in ret])) return ret monkeysign.gpg.Context.build_command = build_command def is_usable(key): unusable = key.invalid or key.disabled \ or key.expired or key.revoked log.debug('Key %s is invalid: %s (i:%s, d:%s, e:%s, r:%s)', key, unusable, key.invalid, key.disabled, key.expired, key.revoked) return not unusable def filter_usable_keys(keys): usable_keys = [Key.from_monkeysign(key) for key in keys if is_usable(key)] log.debug('Identified usable keys: %s', usable_keys) return usable_keys def get_usable_keys_from_keyring(keyring, pattern, public, secret): keys_dict = keyring.get_keys(pattern=pattern, public=public, secret=secret) or {} assert keys_dict is not None, keyring.context.stderr # keys_fpr = keys_dict.items() keys = keys_dict.values() return filter_usable_keys(keys) def sign_keydata(keydata, error_cb=None, homedir=None): """Signs OpenPGP keydata with your regular GnuPG secret keys If error_cb is provided, that function is called with any exception occuring during signing of the key. If error_cb is False, any exception is raised. yields pairs of (uid, signed_uid) """ log = logging.getLogger(__name__ + ':sign_keydata_encrypt') tmpkeyring = TempSigningKeyring(homedir=homedir, base_keyring=Keyring(homedir=homedir)) # Eventually, we want to let the user select their keys to sign with # For now, we just take whatever is there. secret_keys = get_usable_secret_keys(homedir=homedir) log.info('Signing with these keys: %s', secret_keys) stripped_key = MinimalExport(keydata) fingerprint = fingerprint_from_keydata(stripped_key) log.debug('Trying to import key\n%s', stripped_key) if tmpkeyring.import_data(stripped_key): # 3. for every user id (or all, if -a is specified) # 3.1. sign the uid, using gpg-agent keys = tmpkeyring.get_keys(fingerprint) log.info("Found keys %s for fp %s", keys, fingerprint) if len(keys) != 1: raise ValueError("We received multiple keys for fp %s: %s" % (fingerprint, keys)) key = keys[fingerprint] uidlist = key.uidslist for secret_key in secret_keys: secret_fpr = secret_key.fpr log.info('Setting up to sign with %s', secret_fpr) # We need to --always-trust, because GnuPG would print # warning about the trustdb. I think this is because # we have a newly signed key whose trust GnuPG wants to # incorporate into the trust decision. tmpkeyring.context.set_option('always-trust') tmpkeyring.context.set_option('local-user', secret_fpr) # FIXME: For now, we sign all UIDs. This is bad. try: ret = tmpkeyring.sign_key(fingerprint, signall=True) except GpgRuntimeError as e: uid = uidlist[0].uid log.exception("Error signing %r with secret key %r. stdout: %r, stderr: %r", uid, secret_key, tmpkeyring.context.stdout, tmpkeyring.context.stderr) if error_cb: e.uid = uid error_cb (e) else: raise continue log.info("Result of signing %s on key %s: %s", uidlist[0].uid, fingerprint, ret) for uid in uidlist: uid_str = uid.uid log.info("Processing uid %r %s", uid, uid_str) # 3.2. export and encrypt the signature # 3.3. mail the key to the user signed_key = UIDExport(uid_str, tmpkeyring.export_data(uid_str)) log.info("Exported %d bytes of signed key", len(signed_key)) yield (uid, signed_key) ## ## END OF INTERNAL API ##### def openpgpkey_from_data(keydata): "Creates an OpenPGP object from given data" keyring = TempKeyring() if not keyring.import_data(keydata): raise ValueError("Could not import %r - stdout: %r, stderr: %r", keydata, keyring.context.stdout, keyring.context.stderr) # As we have imported only one key, we should also # only have one key at our hands now. keys = keyring.get_keys() if len(keys) != 1: log.debug('Operation on keydata "%s" failed', keydata) raise ValueError("Expected exactly one key, but got %d: %r" % ( len(keys), keys)) else: # The first (key, value) pair in the keys dict # next(iter(keys.items()))[0] might be semantically # more correct than list(d.items()) as we don't care # much about having a list created, but I think it's # more legible. fpr_key = list(keys.items())[0] # is composed of the fpr as key and an OpenPGP key as value key = fpr_key[1] return Key.from_monkeysign(key) def get_public_key_data(fpr, homedir=None): """Returns keydata for a given fingerprint In fact, fpr could be anything that gpg happily exports. """ keyring = Keyring(homedir=homedir) keydata = keyring.export_data(fpr) if not keydata: s = "No data to export for {} (in {})".format(fpr, homedir) raise ValueError(s) return keydata def fingerprint_from_keydata(keydata): '''Returns the OpenPGP Fingerprint for a given key''' openpgpkey = openpgpkey_from_data(keydata) return openpgpkey.fpr def get_usable_keys(pattern="", homedir=None): '''Uses get_keys on the keyring and filters for non revoked, expired, disabled, or invalid keys''' log.debug('Retrieving keys for %s, %s', pattern, homedir) keyring = Keyring(homedir=homedir) return get_usable_keys_from_keyring(keyring=keyring, pattern=pattern, public=True, secret=False) def get_usable_secret_keys(pattern="", homedir=None): '''Returns all secret keys which can be used to sign a key''' keyring = Keyring(homedir=homedir) return get_usable_keys_from_keyring(keyring=keyring, pattern=pattern, public=False, secret=True) def sign_keydata_and_encrypt(keydata, error_cb=None, homedir=None): """Signs OpenPGP keydata with your regular GnuPG secret keys and encrypts the result under the given key error_cb can be a function that is called with any exception occuring during signing of the key. """ tmpkeyring = TempKeyring() tmpkeyring.import_data(keydata) tmpkeyring.context.set_option('always-trust') for (uid, signed_key) in sign_keydata(keydata, error_cb=error_cb, homedir=homedir): if not uid.revoked: encrypted_key = tmpkeyring.encrypt_data(data=signed_key, recipient=uid.uid) yield (UID.from_monkeysign(uid), encrypted_key) gnome-keysign-0.9/keysign/gtkexcepthook.py000066400000000000000000000271351310507150200210420ustar00rootroot00000000000000#!/usr/bin/env python # (c) 2003 Gustavo J A M Carneiro gjc at inescporto.pt # 2004-2005 Filip Van Raemdonck # # http://www.daa.com.au/pipermail/pygtk/2003-August/005775.html # Message-ID: <1062087716.1196.5.camel@emperor.homelinux.net> # "The license is whatever you want." # # This file was downloaded from http://www.sysfs.be/downloads/ # Adaptions 2009-2010 by Martin Renold: # - let KeyboardInterrupt through # - print traceback to stderr before showing the dialog # - nonzero exit code when hitting the "quit" button # - suppress more dialogs while one is already active # - fix Details button when a context in the traceback is None # - remove email features # - fix lockup with dialog.run(), return to mainloop instead # see also http://faq.pygtk.org/index.py?req=show&file=faq20.010.htp # (The license is still whatever you want.) from __future__ import print_function import inspect import linecache import pydoc import sys import traceback if sys.version_info.major < 3: from io import BytesIO as StringIO else: from io import StringIO as StringIO from gettext import gettext as _ import os try: from urllib.parse import quote_plus except ImportError: from urllib import quote_plus import textwrap from gi.repository import Gtk from gi.repository import Gdk from gi.repository import Pango #with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), # '_version.py')) as f: # # This should define __version__ # exec(f.read()) #VERSION = __version__ # We are ignoring the actual version for now, because it's only used # to determine whether it's a -dev version to then show a "report" # button. Let's show this button unconditionally for now. # Once it's too annoying, we get rid of it. VERSION = "0.0.0.1-dev" # Function that will be called when the user presses "Quit" # Return True to confirm quit, False to cancel quit_confirmation_func = None RESPONSE_QUIT = 1 RESPONSE_SEARCH = 2 RESPONSE_REPORT = 3 def analyse_simple(exctyp, value, tb): trace = StringIO() traceback.print_exception(exctyp, value, tb, None, trace) return trace def lookup(name, frame, lcls): '''Find the value for a given name in the given frame''' if name in lcls: return 'local', lcls[name] elif name in frame.f_globals: return 'global', frame.f_globals[name] elif '__builtins__' in frame.f_globals: builtins = frame.f_globals['__builtins__'] if type(builtins) is dict: if name in builtins: return 'builtin', builtins[name] else: if hasattr(builtins, name): return 'builtin', getattr(builtins, name) return None, [] def analyse(exctyp, value, tb): import tokenize import keyword import platform #import application #app = application.get_app() trace = StringIO() nlines = 3 frecs = inspect.getinnerframes(tb, nlines) #trace.write('GNOME Keysign version: %s\n' % app.version) trace.write('System information: %s\n' % platform.platform()) trace.write('Python information: %s\n' % sys.version) #trace.write('Using: %s\n' % (get_libs_version_string(),)) trace.write('Traceback (most recent call last):\n') for frame, fname, lineno, funcname, context, cindex in frecs: trace.write(' File "%s", line %d, ' % (fname, lineno)) args, varargs, varkw, lcls = inspect.getargvalues(frame) def readline(lno=[lineno], *args): if args: print(args) try: return linecache.getline(fname, lno[0]) finally: lno[0] += 1 all, prev, name, scope = {}, None, '', None for ttype, tstr, stup, etup, line in tokenize.generate_tokens(readline): if ttype == tokenize.NAME and tstr not in keyword.kwlist: if name: if name[-1] == '.': try: val = getattr(prev, tstr) except AttributeError: # XXX skip the rest of this identifier only break name += tstr else: assert not name and not scope scope, val = lookup(tstr, frame, lcls) name = tstr if val is not None: prev = val elif tstr == '.': if prev: name += '.' else: if name: all[name] = (scope, prev) prev, name, scope = None, '', None if ttype == tokenize.NEWLINE: break try: details = inspect.formatargvalues(args, varargs, varkw, lcls, formatvalue=lambda v: '=' + pydoc.text.repr(v)) except: # seen that one on Windows (actual exception was KeyError: self) details = '(no details)' trace.write(funcname + details + '\n') if context is None: context = ['\n'] trace.write(''.join([' ' + x.replace('\t', ' ') for x in filter(lambda a: a.strip(), context)])) if len(all): trace.write(' variables: %s\n' % str(all)) trace.write('%s: %s' % (exctyp.__name__, value)) return trace def _info(exctyp, value, tb): global exception_dialog_active if exctyp is KeyboardInterrupt: return original_excepthook(exctyp, value, tb) sys.stderr.write(analyse_simple(exctyp, value, tb).getvalue()) if exception_dialog_active: return Gdk.pointer_ungrab(Gdk.CURRENT_TIME) Gdk.keyboard_ungrab(Gdk.CURRENT_TIME) exception_dialog_active = True # Create the dialog dialog = Gtk.MessageDialog(type=Gtk.MessageType.WARNING) dialog.set_title(_("Bug Detected")) primary = _( "A programming error has been detected." ) secondary = _( "You may be able to ignore this error and carry on working, " "but you may get unexpected results.\n\n" "Please tell the developers about this using the issue tracker " "if no-one else has reported it yet." ) dialog.set_markup(primary) dialog.format_secondary_text(secondary) dialog.add_button(_("Search Tracker..."), RESPONSE_SEARCH) if "-" in VERSION: # only development and prereleases dialog.add_button(_("Report..."), RESPONSE_REPORT) dialog.set_response_sensitive(RESPONSE_REPORT, False) dialog.add_button(_("Ignore Error"), Gtk.ResponseType.CLOSE) dialog.add_button(_("Quit GNOME Keysign"), RESPONSE_QUIT) # Add an expander with details of the problem to the dialog def expander_cb(expander, *ignore): # Ensures that on deactivating the expander, the dialog is resized down if expander.get_expanded(): dialog.set_resizable(True) else: dialog.set_resizable(False) details_expander = Gtk.Expander() details_expander.set_label(_("Details...")) details_expander.connect("notify::expanded", expander_cb) textview = Gtk.TextView() textview.show() textview.set_editable(False) textview.modify_font(Pango.FontDescription("Monospace normal")) sw = Gtk.ScrolledWindow() sw.show() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(textview) # Set window sizing so that it's always at least 600 pixels wide, and # increases by 300 pixels in height once the details panel is open sw.set_size_request(0, 300) dialog.set_size_request(600, 0) details_expander.add(sw) details_expander.show_all() dialog.get_content_area().pack_start(details_expander, True, True, 0) # Get the traceback and set contents of the details try: trace = analyse(exctyp, value, tb).getvalue() except: try: trace = _("Exception while analyzing the exception.") + "\n" trace += analyse_simple(exctyp, value, tb).getvalue() except: trace = _("Exception while analyzing the exception.") buf = textview.get_buffer() trace = "\n".join(["```python", trace, "```"]) buf.set_text(trace) ## Would be nice to scroll to the bottom automatically, but @#&%*@ #first, last = buf.get_bounds() #buf.place_cursor(last) #mark = buf.get_insert() ##buf.scroll_mark_onscreen() ##textview.scroll_mark_onscreen(buf.get_insert(), 0) #textview.scroll_to_mark(mark, 0.0) # Connect callback and present the dialog dialog.connect('response', _dialog_response_cb, trace, exctyp, value) #dialog.set_modal(True) # this might actually be contra-productive... dialog.show() # calling dialog.run() here locks everything up in some cases, so # we just return to the main loop instead def _dialog_response_cb(dialog, resp, trace, exctyp, value): global exception_dialog_active if resp == RESPONSE_QUIT: if not quit_confirmation_func: sys.exit(1) # Exit code is important for IDEs else: if quit_confirmation_func(): sys.exit(1) # Exit code is important for IDEs else: dialog.destroy() exception_dialog_active = False elif resp == RESPONSE_SEARCH: search_url = ( "https://github.com/GNOME-Keysign/gnome-keysign/search" "?utf8=%E2%9C%93" "&q={}+{}" "&type=Issues" ).format( quote_plus(exctyp.__name__, "/"), quote_plus(str(value), "/") ) Gtk.show_uri(None, search_url, Gdk.CURRENT_TIME) if "-" in VERSION: dialog.set_response_sensitive(RESPONSE_REPORT, True) elif resp == RESPONSE_REPORT: #TRANSLATORS: Crash report template for github, preceding a traceback. #TRANSLATORS: Please ask users kindly to supply at least an English #TRANSLATORS: title if they are able. body = _(u"""\ #### Description Give this report a short descriptive title. Use something like "{feature-that-broke}: {what-went-wrong}" for the title, if you can. Then please replace this text with a longer description of the bug. Screenshots or videos are great, too! #### Steps to reproduce Please tell us what you were doing when the error message popped up. If you can provide step-by-step instructions on how to reproduce the bug, that's even better. #### Traceback """) body = "\n\n".join([ "".join(textwrap.wrap(p, sys.maxsize)) for p in textwrap.dedent(body).split("\n\n") ] + [trace]) report_url = ( "https://github.com/GNOME-Keysign/gnome-keysign/issues/new" "?title={title}" "&body={body}" ).format( title="", body=quote_plus(body.encode("utf-8"), "/"), ) Gtk.show_uri(None, report_url, Gdk.CURRENT_TIME) else: dialog.destroy() exception_dialog_active = False original_excepthook = sys.excepthook sys.excepthook = _info exception_dialog_active = False if __name__ == '__main__': import sys import os def _test_button_clicked_cb(*a): class _TestException (Exception): pass raise _TestException("That was supposed to happen.") win = Gtk.Window() win.set_size_request(200, 150) win.set_title(os.path.basename(sys.argv[0])) btn = Gtk.Button("Break it") btn.connect("clicked", _test_button_clicked_cb) win.add(btn) win.connect("destroy", lambda *a: Gtk.main_quit()) win.show_all() Gtk.main() gnome-keysign-0.9/keysign/keyconfirm.py000066400000000000000000000135731310507150200203320ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # Copyright 2016 Andrei Macavei # Copyright 2017 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from datetime import date, datetime import signal import sys import argparse import logging import os import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib from gi.repository import GObject if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .gpgmh import get_usable_keys from .scan_barcode import ScalingImage from .util import format_fingerprint log = logging.getLogger(__name__) #FIXME: remove the temporary keyword args after updating Key class #with length and creation_time fields def format_key_header(fpr, length='2048', creation_time=None): if creation_time == None: creation_time = datetime.strptime('01011970', "%d%m%Y").date() try: creation = date.fromtimestamp(float(creation_time)) except TypeError as e: # This might be the case when the creation_time is already a timedate creation = creation_time key_header = format_fingerprint(fpr).replace('\n', ' ') return key_header def format_uidslist(uidslist): result = "" for uid in uidslist: uidstr = GLib.markup_escape_text(str(uid)) result += ("{}\n".format(uidstr)) return result class PreSignWidget(Gtk.VBox): """A widget for obtaining a key fingerprint. The fingerprint can be obtain by inserting it into a text entry, or by scanning a barcode with the built-in camera. """ __gsignals__ = { str('sign-key-confirmed'): (GObject.SIGNAL_RUN_LAST, None, (GObject.TYPE_PYOBJECT,)), } def __init__(self, key, pixbuf=None, builder=None): super(PreSignWidget, self).__init__() thisdir = os.path.dirname(os.path.abspath(__file__)) widget_name = 'keyconfirmbox' if not builder: builder = Gtk.Builder() builder.add_objects_from_file( os.path.join(thisdir, 'receive.ui'), [widget_name, 'confirm-button-image']) widget = builder.get_object(widget_name) parent = widget.get_parent() if parent: parent.remove(widget) self.add(widget) confirm_btn = builder.get_object("confirm_sign_button") confirm_btn.connect("clicked", self.on_confirm_button_clicked) self.key = key keyIdsLabel = builder.get_object("key_ids_label") log.info("The Key ID Label can focus: %r, %r", keyIdsLabel.props.can_focus, keyIdsLabel.get_can_focus()) # Weird. The glade file defines can_focus = False, but it's set to True... keyIdsLabel.set_can_focus(False) keyIdsLabel.set_markup(format_key_header(self.key.fingerprint)) uidsLabel = builder.get_object("uids_label") # FIXME: Check why Builder thinks the widget can focus when the glade file says no uidsLabel.set_can_focus(False) markup = format_uidslist(self.key.uidslist) uidsLabel.set_markup(markup) imagebox = builder.get_object("imagebox") for child in imagebox.get_children(): imagebox.remove(child) imagebox.add(ScalingImage(pixbuf=pixbuf)) imagebox.show_all() def on_confirm_button_clicked(self, buttonObject, *args): self.emit('sign-key-confirmed', self.key, *args) class PreSignApp(Gtk.Application): def __init__(self, *args, **kwargs): super(PreSignApp, self).__init__(*args, **kwargs) self.connect('activate', self.on_activate) self.psw = None self.log = logging.getLogger(__name__) def on_activate(self, app): window = Gtk.ApplicationWindow() window.set_title("Key Pre Sign Widget") # window.set_size_request(600, 400) if not self.psw: self.psw = PreSignWidget() self.psw.connect('sign-key-confirmed', self.on_sign_key_confirmed) window.add(self.psw) window.show_all() self.add_window(window) def on_sign_key_confirmed(self, keyPreSignWidget, *args): self.log.debug ("Sign key confirmed!") def run(self, args): if not args: args = [""] key = get_usable_keys (pattern=args[0])[0] if len(args) >= 2: image_fname = args[1] log.debug("Trying to load pixbuf from %r", image_fname) pixbuf = Gtk.Image.new_from_file(image_fname).get_pixbuf() else: pixbuf = None self.psw = PreSignWidget(key, pixbuf=pixbuf) super(PreSignApp, self).run() if __name__ == "__main__": import sys logging.basicConfig(level=logging.DEBUG) app = PreSignApp() app.run(sys.argv[1:]) gnome-keysign-0.9/keysign/keyfprscan.py000066400000000000000000000130511310507150200203200ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # Copyright 2016 Andrei Macavei # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import sys import logging import os import gi gi.require_version('Gtk', '3.0') gi.require_version('Gst', '1.0') from gi.repository import Gtk, Gst, GdkPixbuf from gi.repository import GObject if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .scan_barcode import BarcodeReaderGTK log = logging.getLogger(__name__) class KeyFprScanWidget(Gtk.VBox): """A widget for obtaining a key fingerprint. The fingerprint can be obtain by inserting it into a text entry, or by scanning a barcode with the built-in camera. """ __gsignals__ = { # This is the Gtk widget signal's name str('changed'): (GObject.SIGNAL_RUN_LAST, None, (GObject.TYPE_PYOBJECT,)), # It's probably not the best name for that signal. # While "barcode_scanned" might be better, it is probably # unneccesarily specific. str('barcode'): (GObject.SIGNAL_RUN_LAST, None, (str, # The barcode string Gst.Message.__gtype__, # The GStreamer message itself GdkPixbuf.Pixbuf.__gtype__,),) # The pixbuf which caused # the above string to be decoded } def __init__(self, builder=None): log.debug("Init KFSW %r %r", self, builder) if issubclass(self.__class__, object): super(KeyFprScanWidget, self).__init__() else: Gtk.VBox.__init__(self) log.debug("Inited parent KFSW %r", self) widget_name = 'scanner_widget' if not builder: thisdir = os.path.dirname(os.path.abspath(__file__)) builder = Gtk.Builder() builder.add_objects_from_file(os.path.join(thisdir, 'receive.ui'), [widget_name]) widget = builder.get_object(widget_name) parent = widget.get_parent() if parent: parent.remove(widget) self.add(widget) self.scanner = builder.get_object("scanner") if not Gst.is_initialized(): log.error("Gst does not seem to be initialised. Call Gst.init()!") # This needs to be called before creating a BarcodeReaderGTK Gst.init() reader = BarcodeReaderGTK() reader.set_size_request(150,150) reader.connect('barcode', self.on_barcode) self.scanner.add(reader) # We keep a referece here to not "lose" the object. # If we don't, Gtk crashes. With a segfault. Probably # because the object is freed but still used. # Somebody should look at that... self.reader = reader self.fpr_entry = builder.get_object("fingerprint_entry") self.fpr_entry.connect('changed', self.on_text_changed) self.set_hexpand(True) self.set_vexpand(True) # Temporary measure... self.barcode_scanner = self def on_text_changed(self, entryObject, *args): self.emit('changed', entryObject, *args) def on_barcode(self, sender, barcode, message, image): self.emit('barcode', barcode, message, image) def get_text(self): "Returns the text present in the Entry" text = self.fpr_entry.get_text() return text class KeyScanApp(Gtk.Application): def __init__(self, *args, **kwargs): super(KeyScanApp, self).__init__(*args, **kwargs) self.connect('activate', self.on_activate) self.scanwidget = None self.log = logging.getLogger(__name__) def on_activate(self, app): window = Gtk.ApplicationWindow() window.set_title("Key Fingerprint Scanner Widget") window.set_size_request(600, 400) if not self.scanwidget: self.scanwidget = KeyFprScanWidget() self.scanwidget.connect('changed', self.on_text_changed) self.scanwidget.connect('barcode', self.on_barcode) window.add(self.scanwidget) window.show_all() self.add_window(window) def on_text_changed(self, keyFprScanWidget, entryObject, *args): self.log.debug ("Text changed! %s" % (entryObject.get_text(),)) def on_barcode(self, sender, barcode, message, image): self.log.debug ("Barcode signal %r %r", barcode, message) if __name__ == "__main__": import sys logging.basicConfig(level=logging.DEBUG) Gst.init() app = KeyScanApp() app.run() gnome-keysign-0.9/keysign/keylistwidget.py000066400000000000000000000135301310507150200210450ustar00rootroot00000000000000#!/usr/bin/env python import logging import os import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import GObject # for __gsignals__ from gi.repository import GLib # for markup_escape_text if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .gpgmh import get_usable_keys log = logging.getLogger(__name__) class ListBoxRowWithKey(Gtk.ListBoxRow): "A simple extension of a Gtk.ListBoxRow to also hold a key object" def __init__(self, key): super(ListBoxRowWithKey, self).__init__() self.key = key s = self.format(key) label = Gtk.Label(s, use_markup=True, xalign=0) self.add(label) @classmethod def format_uid(cls, uid): "Returns a pango string for a gpgmh.UID" fmt = "{name}\t{email}\t{expiry}" d = {k: GLib.markup_escape_text("{}".format(v)) for k, v in uid._asdict().items()} log.info("Formatting UID %r", d) s = fmt.format(**d) log.info("Formatted UID: %r", s) return s @classmethod def format(cls, key): "Returns a pango string for a gpgmh.Key" fmt = "{created} " fmt = "{fingerprint}\n" fmt += "\n".join((cls.format_uid(uid) for uid in key.uidslist)) fmt += "\nExpires {expiry}" d = {k: GLib.markup_escape_text("{}".format(v)) for k,v in key._asdict().items()} log.info("Formatting key %r", d) s = fmt.format(**d) log.info("Formatted key: %r", s) return s class KeyListWidget(Gtk.HBox): """A Gtk Widget representing a list of OpenPGP Keys It shows the keys you provide in a ListBox and emits a `key-activated` or `key-selected` signal when the user "activated" or "selected" a key. "Activating" is Gtk speak for double-clicking (or pressing space, enter, ...) on an entry. It is also possible that the widget emits that signal on a single click if so configured. "Selected" means that the user switched to an entry, e.g. by clicking it or pressing up or down. If you don't provide any keys, the widget will not behave nicely and potentially display a user facing warning. Or not. """ __gsignals__ = { str('key-activated'): (GObject.SIGNAL_RUN_LAST, None, # (ListBoxRowWithKey.__gtype__,) (object,)), # The activated key str('key-selected'): (GObject.SIGNAL_RUN_LAST, None, # (ListBoxRowWithKey.__gtype__,) (object,)), # The selected key } def __init__(self, keys, builder=None): "Sets the widget up with the given keys" super(KeyListWidget, self).__init__() self.log = logging.getLogger(__name__) self.log.debug("KLW with keys: %r", keys) thisdir = os.path.dirname(os.path.abspath(__file__)) widget_name = 'keylistbox' if not builder: builder = Gtk.Builder() builder.add_objects_from_file( os.path.join(thisdir, 'send.ui'), [widget_name]) widget = builder.get_object(widget_name) old_parent = widget.get_parent() if old_parent: old_parent.remove(widget) self.add(widget) self.listbox = builder.get_object("keys_listbox") if len(list(keys)) <= 0: infobar = builder.get_object("infobar") infobar.show() l = Gtk.Label("You don't have any OpenPGP keys") self.listbox.add(l) else: for key in keys: self.log.debug("Adding key: %r", key) lbr = ListBoxRowWithKey(key) lbr.props.margin_bottom = 5 self.listbox.add(lbr) self.listbox.connect('row-activated', self.on_row_activated) self.listbox.connect('row-selected', self.on_row_selected) def on_row_activated(self, keylistwidget, row): if row: self.emit('key-activated', row.key) def on_row_selected(self, keylistwidget, row): if row: self.emit('key-selected', row.key) class App(Gtk.Application): def __init__(self, *args, **kwargs): super(App, self).__init__(*args, **kwargs) self.connect('activate', self.on_activate) self.kpw = None def on_activate(self, app): window = Gtk.ApplicationWindow() window.set_title("Key List") if not self.kpw: self.kpw = KeyListWidget(get_usable_keys()) self.kpw.connect('key-activated', self.on_key_activated) self.kpw.connect('key-selected', self.on_key_selected) window.add(self.kpw) window.show_all() self.add_window(window) def on_key_activated(self, keylistwidget, row): self.get_windows()[0].get_window().beep() print ("Row activated! %r" % (row,)) def on_key_selected(self, keylistwidget, row): print ("Row selected! %r" % (row,)) def run(self, args): if not args: args = [""] keys = list(get_usable_keys(pattern=args[0])) self.kpw = KeyListWidget(keys) super(App, self).run() if __name__ == "__main__": import sys logging.basicConfig(level=logging.DEBUG) app = App() app.run(sys.argv[1:]) gnome-keysign-0.9/keysign/network/000077500000000000000000000000001310507150200172725ustar00rootroot00000000000000gnome-keysign-0.9/keysign/network/AvahiBrowser.py000066400000000000000000000125001310507150200222360ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # Copyright 2014 Andrei Macavei # Copyright 2015 Jody Hansen # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from __future__ import print_function import avahi, dbus from dbus import DBusException from dbus.mainloop.glib import DBusGMainLoop from gi.repository import Gio from gi.repository import GObject import logging __all__ = ["AvahiBrowser"] DBusGMainLoop( set_as_default=True ) # This should probably be upstreamed. # Unfortunately, upstream seems rather inactive. if getattr(avahi, 'txt_array_to_dict', None) is None: # This has been taken from Gajim # http://hg.gajim.org/gajim/file/4a3f896130ad/src/common/zeroconf/zeroconf_avahi.py # it is licensed under the GPLv3. def txt_array_to_dict(txt_array): txt_dict = {} for els in txt_array: key, val = '', None for c in els: #FIXME: remove when outdated, this is for avahi < 0.6.14 if c < 0 or c > 255: c = '.' else: c = chr(c) if val is None: if c == '=': val = '' else: key += c else: val += c if val is None: # missing '=' val = '' txt_dict[key] = val.decode('utf-8') return txt_dict setattr(avahi, 'txt_array_to_dict', txt_array_to_dict) class AvahiBrowser(GObject.GObject): __gsignals__ = { str('new_service'): (GObject.SIGNAL_RUN_LAST, None, # name, address (could be an int too (for IPv4)), port, txt_dict (str, str, int, object)), str('remove_service'): (GObject.SIGNAL_RUN_LAST, None, # string 'remove'(placeholder: tuple element must be sequence), name (str, str)), } def __init__(self, loop=None, service='_gnome-keysign._tcp'): GObject.GObject.__init__(self) self.log = logging.getLogger(__name__) self.service = service # It seems that these are different loops..?! self.loop = loop or DBusGMainLoop() self.bus = dbus.SystemBus(mainloop=self.loop) self.server = dbus.Interface( self.bus.get_object(avahi.DBUS_NAME, '/'), 'org.freedesktop.Avahi.Server') self.sbrowser = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, self.service, 'local', dbus.UInt32(0))), avahi.DBUS_INTERFACE_SERVICE_BROWSER) self.sbrowser.connect_to_signal("ItemNew", self.on_new_item) self.sbrowser.connect_to_signal("ItemRemove", self.on_service_removed) def on_new_item(self, interface, protocol, name, stype, domain, flags): self.log.info("Found service '%s' type '%s' domain '%s' ", name, stype, domain) if flags & avahi.LOOKUP_RESULT_LOCAL: # FIXME skip local services pass self.server.ResolveService(interface, protocol, name, stype, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), reply_handler=self.on_service_resolved, error_handler=self.on_error) def on_service_resolved(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): '''called when the browser successfully found a service''' txt = avahi.txt_array_to_dict(txt) self.log.info("Service resolved; name: '%s', address: '%s'," "port: '%s', and txt: '%s'", name, address, port, txt) retval = self.emit('new_service', name, address, port, txt) self.log.info("emitted '%s'", retval) def on_service_removed(self, interface, protocol, name, stype, domain, flags): '''Emits items to be removed from list of discovered services.''' self.log.info("Service removed; name: '%s'", name) retval = self.emit('remove_service', 'remove', name) self.log.info("emitted '%s'", retval) def on_error(self, *args): print('error_handler') print(args[0]) def main(): loop = GObject.MainLoop() # We're not passing the loop to DBus, because... well, it # does't work... It seems to expect a DBusMainLoop, not # an ordinary main loop... ab = AvahiBrowser() def print_signal(*args): print("Signal ahoi", args) ab.connect('new_service', print_signal) ab.connect('remove_service', print_signal) loop.run() if __name__ == "__main__": main() gnome-keysign-0.9/keysign/network/AvahiPublisher.py000066400000000000000000000123361310507150200225570ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # Copyright 2014 Andrei Macavei # Copyright 2015 Jody Hansen # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import avahi import dbus from dbus.mainloop.glib import DBusGMainLoop from gi.repository import GObject DBusGMainLoop( set_as_default=True ) class AvahiPublisher: def __init__(self, service_name='Demo Service', service_type='_demo._tcp', service_port=8899, service_txt={}, domain='', host=''): self.log = logging.getLogger(__name__) #self.loop = loop or DBusGMainLoop() self.bus = dbus.SystemBus() self.server = dbus.Interface( self.bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ), avahi.DBUS_INTERFACE_SERVER ) self.service_name = service_name #See http://www.dns-sd.org/ServiceTypes.html self.service_type = service_type self.service_port = service_port self.service_txt = avahi.dict_to_txt_array(service_txt) #TXT record for the service self.domain = domain # Domain to publish on, default to .local self.host = host # Host to publish records for, default to localhost self.group = None # Counter so we only rename after collisions a sensible number of times self.rename_count = 12 def add_service(self): if self.group is None: group = dbus.Interface( self.bus.get_object( avahi.DBUS_NAME, self.server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP) group.connect_to_signal('StateChanged', self.entry_group_state_changed) self.group = group self.log.info("Adding service '%s' of type '%s' with fpr '%s'", self.service_name, self.service_type, self.service_txt) group = self.group group.AddService( avahi.IF_UNSPEC, #interface avahi.PROTO_UNSPEC, #protocol dbus.UInt32 (0), #flags self.service_name, self.service_type, self.domain, self.host, dbus.UInt16 (self.service_port), self.service_txt) group.Commit() def remove_service(self): '''Publishes services to be removed with name, stype, and domain.''' self.log.info("Removing with fpr '%s'", self.service_txt) if not self.group is None: self.group.Reset() def server_state_changed(self, state): if state == avahi.SERVER_COLLISION: self.log.warn("Server name collision (%s)", self.service_name) self.remove_service() elif state == avahi.SERVER_RUNNING: self.add_service() def entry_group_state_changed(self, state, error): self.log.debug("state change: %i", state) if state == avahi.ENTRY_GROUP_ESTABLISHED: self.log.info("Service established.") elif state == avahi.ENTRY_GROUP_COLLISION: self.rename_count -= 1 if self.rename_count > 0: name = self.server.GetAlternativeServiceName(self.service_name) self.log.warn("Service name collision, changing name to '%s'", name) self.remove_service() self.add_service() else: # FIXME: max_renames is not defined. We probably want to restructure # this a little bit, anyway. i.e. have a self.max_renames # and a self.rename_count or so m = "No suitable service name found after %i retries, exiting." self.log.error(m, self.max_renames) raise RuntimeError(m % self.max_renames) elif state == avahi.ENTRY_GROUP_FAILURE: m = "Error in group state changed %s" self.log.error(m, error) raise RuntimeError(m % error) DBusGMainLoop( set_as_default=True ) if __name__ == '__main__': ap = AvahiPublisher() ap.add_service() main_loop = GObject.MainLoop() bus = dbus.SystemBus() server = dbus.Interface( bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ), avahi.DBUS_INTERFACE_SERVER ) server.connect_to_signal( "StateChanged", ap.server_state_changed ) ap.server_state_changed( server.GetState() ) try: main_loop.run() except KeyboardInterrupt: pass if not ap.group is None: ap.group.Free() gnome-keysign-0.9/keysign/network/__init__.py000066400000000000000000000015311310507150200214030ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Tobias Mueller # Copyright 2014 Andrei Macavei # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . gnome-keysign-0.9/keysign/receive.py000066400000000000000000000150021310507150200175730ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import re import os import signal import sys import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib gi.require_version('Gst', '1.0') from gi.repository import Gst if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .avahidiscovery import AvahiKeysignDiscoveryWithMac from .keyfprscan import KeyFprScanWidget from .keyconfirm import PreSignWidget from .gpgmh import openpgpkey_from_data from .util import sign_keydata_and_send log = logging.getLogger(__name__) def remove_whitespace(s): cleaned = re.sub('[\s+]', '', s) return cleaned class ReceiveApp: def __init__(self, builder=None): self.psw = None self.discovery = None self.log = logging.getLogger(__name__) widget_name = "receive_stack" if not builder: ui_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "receive.ui") builder = Gtk.Builder() builder.add_objects_from_file(ui_file, [widget_name, 'confirm-button-image']) self.accept_button = builder.get_object("confirm_sign_button") old_scanner = builder.get_object("scanner_widget") old_scanner_parent = old_scanner.get_parent() scanner = KeyFprScanWidget() #builder=builder) scanner.connect("changed", self.on_scanner_changed) scanner.connect("barcode", self.on_barcode) if old_scanner_parent: old_scanner_parent.remove(old_scanner) # Hm. If we don't have an old parent, we never get to see # the newly created scanner. Weird. old_scanner_parent.add(scanner) receive_stack = builder.get_object(widget_name) # It needs to be show()n so that it can be made visible scanner.show() # FIXME: Use "stack_scanner_child" or so as identification # for the stack's scanner child to make it visible when the # app starts # receive_stack.set_visible_child(old_scanner_parent) self.scanner = scanner self.stack = receive_stack self.discovery = AvahiKeysignDiscoveryWithMac() ib = builder.get_object('infobar_discovery') self.discovery.connect('list-changed', self.on_list_changed, ib) def on_keydata_downloaded(self, keydata, pixbuf=None): key = openpgpkey_from_data(keydata) psw = PreSignWidget(key, pixbuf) psw.connect('sign-key-confirmed', self.on_sign_key_confirmed, keydata) self.stack.add_titled(psw, "presign", "Sign Key") psw.set_name("presign") psw.show() self.psw = psw self.stack.set_visible_child(self.psw) def on_scanner_changed(self, scanner, entry): self.log.debug("Entry changed %r: %r", scanner, entry) text = entry.get_text() keydata = self.discovery.find_key(text) if keydata: self.on_keydata_downloaded(keydata) def on_barcode(self, scanner, barcode, gstmessage, pixbuf): self.log.debug("Scanned barcode %r", barcode) keydata = self.discovery.find_key(barcode) if keydata: self.on_keydata_downloaded(keydata, pixbuf) def on_sign_key_confirmed(self, keyPreSignWidget, key, keydata): self.log.debug ("Sign key confirmed! %r", key) # We need to prevent tmpfiles from going out of # scope too early so that they don't get deleted self.tmpfiles = list( sign_keydata_and_send(keydata)) # After the user has signed, we switch back to the scanner, # because currently, there is not much to do on the # key confirmation page. log.debug ("Signed the key: %r", self.tmpfiles) self.stack.set_visible_child_name("scanner") # Do we also want to add an infobar message or so..? def on_list_changed(self, discovery, number, userdata): ib = userdata if number == 0: ib.show() elif ib.is_visible(): ib.hide() class App(Gtk.Application): def __init__(self, *args, **kwargs): super(App, self).__init__(*args, **kwargs) self.connect('activate', self.on_activate) self.log = logging.getLogger(__name__) def on_activate(self, app): ui_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "receive.ui") builder = Gtk.Builder.new_from_file(ui_file) window = Gtk.ApplicationWindow() window.set_title("Receive") # window.set_size_request(600, 400) #window = self.builder.get_object("appwindow") self.receive = ReceiveApp(builder) receive_stack = self.receive.stack window.add(receive_stack) window.show_all() self.add_window(window) def main(args=[]): log = logging.getLogger(__name__) log.debug('Running main with args: %s', args) if not args: args = [] Gst.init() app = App() try: GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: pass app.run(args) if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') sys.exit(main(sys.argv[1:])) gnome-keysign-0.9/keysign/receive.ui000066400000000000000000000661111310507150200175670ustar00rootroot00000000000000 True False gtk-apply True False slide-left-right scanner True False vertical False True False 6 end False False 0 False 16 True False No GNOME Keysign servers around :-( Find a friend to use GNOME Keysign with. You may also suffer from connectivity problems. For more information click <a href="https://wiki.gnome.org/Apps/Keysign/Doc/NoServers/1">here</a>. True False True 0 False False 0 False True 0 True False 100 100 10 10 vertical 6 True False <small>To sign someone's key, scan their QR or enter security code</small> True 0 False True 1 True False 20 True False <b>Camera</b> True False True 0 True False 0 Integrated Web Cam True True end 1 False True 2 True False vertical True True 3 True False start <b>Security Code</b> True 0 False True 4 True True False True 5 False True 1 scanner Scan Barcode True False 20 20 10 10 vertical 6 True False Downloading key-data. Please wait ... False True 0 True False True True 1 False Key download was interrupted! False True 2 True False gtk-cancel True True True True True False True end 0 gtk-redo True True True True True False True end 1 False True 3 page1 page1 1 Confirm Signing Key True False 20 20 10 10 vertical 6 True False vertical True False To sign the key, confirm that you want to sign the following key. This will generate an email that must be sent in order to complete the signing process. True 0 False True 0 True False 15 2 3 True False vertical True False start Key 0 False True 0 200 True False 23FD 347A 4194 29BA CCD5 E72D 6BC4 7780 54AC D246 True 0 False True 1 True False start 5 UIDs 0 False True 2 True False Zulu Test <foo@example.com> Alpha Bar <example@example.com> True 0 False True 3 False True 0 200 200 True False 5 immediate vertical 200 200 True False end gtk-missing-image False True 0 True True 2 1 True True 1 True True 0 True False 6 C_onfirm confirm_button True True True True True confirm-button-image True True False True end 0 False True end 5 page2 page2 2 True False 20 20 10 10 vertical 6 True False Signing the following UIDs: False True 0 True False False True 1 True False True True 2 False False True 3 True False gtk-cancel True True True True True False True end 0 False True end 4 page3 page3 3 gnome-keysign-0.9/keysign/scan_barcode.py000066400000000000000000000314751310507150200205700ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014, 2015 Tobias Mueller # Copyright 2014 Andrei Macavei # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import signal import sys import gi gi.require_version('Gst', '1.0') gi.require_version('GstVideo', '1.0') from gi.repository import GObject from gi.repository import Gst from gi.repository import Gtk, GLib # Because of https://bugzilla.gnome.org/show_bug.cgi?id=698005 from gi.repository import Gtk, GdkPixbuf from gi.repository import GstVideo from gi.repository import Gdk log = logging.getLogger(__name__) class BarcodeReaderGTK(Gtk.Box): __gsignals__ = { str('barcode'): (GObject.SIGNAL_RUN_LAST, None, (str, # The barcode string Gst.Message.__gtype__, # The GStreamer message itself GdkPixbuf.Pixbuf.__gtype__, # The pixbuf which caused # the above string to be decoded ), ) } def __init__(self, *args, **kwargs): super(BarcodeReaderGTK, self).__init__(*args, **kwargs) self.connect('unmap', self.on_unmap) self.connect('map', self.on_map) def on_message(self, bus, message): #log.debug("Message: %s", message) if message: struct = message.get_structure() if struct: struct_name = struct.get_name() #log.debug('Message name: %s', struct_name) if struct_name == 'GstMessageError': err, debug = message.parse_error() log.error('GstError: %s, %s', err, debug) elif struct_name == 'GstMessageWarning': err, debug = message.parse_warning() log.warning('GstWarning: %s, %s', err, debug) elif struct_name == 'barcode': self.timestamp = struct.get_clock_time("timestamp")[1] log.debug ("at %s", self.timestamp) assert struct.has_field('symbol') barcode = struct.get_string('symbol') log.info("Read Barcode: {}".format(barcode)) pixbuf = None if struct.has_field ("frame"): # This is the new zbar, which posts the frame along # with the barcode. sample = struct.get_value ("frame") pixbuf = gst_sample_to_pixbuf(sample) self.emit("barcode", barcode, message, pixbuf) else: # If we do not see the zbar < 1.6, we raise raise def run(self): p = "autovideosrc \n" #p = "uridecodebin uri=file:///tmp/qr.png " #p = "uridecodebin uri=file:///tmp/v.webm " p += " ! tee name=t \n" p += " t. ! queue ! videoconvert \n" p += " ! zbar cache=true attach_frame=true \n" p += " ! fakesink \n" p += " t. ! queue ! videoconvert \n" p += (" ! gtksink " "sync=false " "name=imagesink " #"max-lateness=2000000000000 " "enable-last-sample=false " "\n" ) pipeline = p log.info("Launching pipeline %s", pipeline) pipeline = Gst.parse_launch(pipeline) self.imagesink = pipeline.get_by_name('imagesink') self.gtksink_widget = self.imagesink.get_property("widget") log.info("About to remove children from %r", self) for child in self.get_children(): log.info("About to remove child: %r", child) self.remove(child) # self.gtksink_widget.set_property("expand", False) log.info("Adding sink widget: %r", self.gtksink_widget) #self.add(self.gtksink_widget) self.pack_start(self.gtksink_widget, True, True, 0) self.gtksink_widget.show() self.pipeline = pipeline bus = pipeline.get_bus() bus.connect('message', self.on_message) bus.add_signal_watch() pipeline.set_state(Gst.State.PLAYING) def pause(self): self.pipeline.set_state(Gst.State.PAUSED) def on_map(self, *args, **kwargs): '''It seems this is called when the widget is becoming visible''' self.run() def on_unmap(self, *args, **kwargs): '''Hopefully called when this widget is hidden, e.g. when the tab of a notebook has changed''' self.pipeline.set_state(Gst.State.PAUSED) # Actually, we stop the thing for real self.pipeline.set_state(Gst.State.NULL) def do_barcode(self, barcode, message, image): "This is called by GObject, I think" log.debug("Emitting a barcode signal %s, %s, %r", barcode, message, image) class ReaderApp(Gtk.Application): '''A simple application for scanning a bar code It makes use of the BarcodeReaderGTK class and connects to its on_barcode signal. You need to have called Gst.init() before creating a BarcodeReaderGTK. ''' def __init__(self, *args, **kwargs): super(ReaderApp, self).__init__(*args, **kwargs) self.connect('activate', self.on_activate) def on_activate(self, data=None): window = Gtk.ApplicationWindow() window.set_title("Gtk Gst Barcode Reader") reader = BarcodeReaderGTK() reader.connect('barcode', self.on_barcode) window.add(reader) window.show_all() self.add_window(window) def on_barcode(self, reader, barcode, message, image): '''All we do is logging the decoded barcode''' logging.info('Barcode decoded: %s', barcode) class SimpleInterface(ReaderApp): '''We tweak the UI of the demo ReaderApp a little''' def on_activate(self, *args, **kwargs): window = Gtk.ApplicationWindow() window.set_title("Simple Barcode Reader") window.set_default_size(400, 300) vbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, 0) vbox.set_margin_top(3) vbox.set_margin_bottom(3) window.add(vbox) reader = BarcodeReaderGTK() reader.connect('barcode', self.on_barcode) vbox.pack_start(reader, True, True, 0) self.reader = reader #self.image = Gtk.Image() # FIXME: We could show a default image like "no barcode scanned just yet" self.image = ScalingImage() self.imagebox = Gtk.Box() #expand=True) self.imagebox.add(self.image) self.imagebox.show() vbox.pack_end(self.imagebox, True, True, 0) self.playButtonImage = Gtk.Image() self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON) self.playButton = Gtk.Button.new() self.playButton.add(self.playButtonImage) self.playButton.connect("clicked", self.playToggled) vbox.pack_end(self.playButton, False, False, 0) window.show_all() self.add_window(window) def playToggled(self, w): self.reader.pause() def on_barcode(self, reader, barcode, message, pixbuf): log.info("Barcode!!1 %r", barcode) # Hrm. Somehow, the Gst Widget is allocating # space relatively aggressively. Our imagebox on # the right side does not get any space. #self.imagebox.remove(self.image) #self.image = ScalingImage(pixbuf) #self.imagebox.pack_start(self.image, True, True, 0) #self.image.set_property('expand', True) #self.image.show() self.image.set_from_pixbuf(pixbuf) # So we just show a window instead... w = Gtk.Window() w.add(ScalingImage(pixbuf)) w.show_all() return False def gst_sample_to_pixbuf(sample): '''Converts the image from a given GstSample to a GdkPixbuf''' caps = Gst.Caps.from_string("video/x-raw,format=RGBA") converted_sample = GstVideo.video_convert_sample(sample, caps, Gst.CLOCK_TIME_NONE) buffer = converted_sample.get_buffer() pixbuf = buffer.extract_dup(0, buffer.get_size()) caps = converted_sample.get_caps() struct = caps.get_structure(0) colorspace = GdkPixbuf.Colorspace.RGB alpha = True bps = 8 width_struct = struct.get_int("width") assert width_struct[0] height_struct = struct.get_int("height") assert height_struct[0] original_width = width_struct[1] original_height = height_struct[1] rowstride_struct = struct.get_int("stride") if rowstride_struct[0] == True: # The stride information might be hidden in the struct. # For now it doesn't work. I think it's the name of the field. rowstride = rowstride_struct[1] else: rowstride = bps / 8 * 4 * original_width gdkpixbuf = GdkPixbuf.Pixbuf.new_from_bytes( GLib.Bytes.new_take(pixbuf), colorspace, alpha, bps, original_width, original_height, rowstride) return gdkpixbuf class ScalingImage(Gtk.DrawingArea): def __init__(self, pixbuf=None, width=None, height=None, rowstride=None): self.pixbuf = pixbuf self.rowstride = rowstride or None super(ScalingImage, self).__init__() #self.set_property("width_request", 400) #self.set_property("height_request", 400) #self.set_property("margin", 10) self.set_property("expand", True) def set_from_pixbuf(self, pixbuf): self.pixbuf = pixbuf self.queue_draw() # def do_size_allocate(self, allocation): # log.debug("Size Allocate %r", allocation) # log.debug("w: %r h: %r", allocation.width, allocation.height) # self.queue_draw() def do_draw(self, cr, pixbuf=None): log.debug('Drawing ScalingImage! %r', self) pixbuf = pixbuf or self.pixbuf if not pixbuf: log.info('No pixbuf to draw! %r', pixbuf) else: original_width = pixbuf.get_width() original_height = pixbuf.get_height() assert original_width > 0 assert original_height > 0 # Scale the pixbuf down to whatever space we have allocation = self.get_allocation() widget_width = allocation.width widget_height = allocation.height # I think we might not need this calculation #widget_size = min(widget_width, widget_height) log.info('Allocated size: %s, %s', widget_width, widget_height) # Fill in background cr.save() #Gtk.render_background(self.get_style_context(), # cr, 0, 0, widget_width, widget_height) #cr.set_source_rgb(1, 1, 1) #cr.paint() # Centering and scaling the image to fit the widget cr.translate(widget_width / 2.0, widget_height / 2.0) scale = min(widget_width / float(original_width), widget_height / float(original_height)) cr.scale(scale, scale) cr.translate(-original_width / 2.0, -original_height / 2.0) # Note: This function is very inefficient # (one could cache the resulting pattern or image surface)! Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0) # Should anyone want to set filters, this is the way to do it. #pattern = cr.get_source() #pattern.set_filter(cairo.FILTER_NEAREST) cr.paint() cr.restore() return #super(ScalingImage, self).do_draw(cr) def main(): logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s (%(levelname)s): %(message)s') # We need to have GStreamer initialised before creating a BarcodeReader Gst.init(sys.argv) app = SimpleInterface() try: # Exit the mainloop if Ctrl+C is pressed in the terminal. GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: # Whatever, it is only to enable Ctrl+C anyways pass app.run() if __name__ == '__main__': main() gnome-keysign-0.9/keysign/send.py000066400000000000000000000136241310507150200171120ustar00rootroot00000000000000#!/usr/bin/env python import logging import os import signal import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import GLib # for markup_escape_text if __name__ == "__main__" and __package__ is None: logging.getLogger().error("You seem to be trying to execute " + "this script directly which is discouraged. " + "Try python -m instead.") parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.sys.path.insert(0, parent_dir) os.sys.path.insert(0, os.path.join(parent_dir, 'monkeysign')) import keysign #mod = __import__('keysign') #sys.modules["keysign"] = mod __package__ = str('keysign') from .keylistwidget import KeyListWidget from .KeyPresent import KeyPresentWidget from .avahioffer import AvahiHTTPOffer from . import gpgmh log = logging.getLogger(__name__) class SendApp: """Common functionality needed when building the sending part This class will automatically start the keyserver and avahi components. It will load a GtkStack from "send.ui" and automatically switch to a newly generate KeyPresentWidget. To switch the stack back and stop the keyserver, you have to call deactivate(). """ def __init__(self, builder=None): self.avahi_offer = None self.stack = None self.stack_saved_visible_child = None self.klw = None self.kpw = None ui_file_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "send.ui") if not builder: builder = Gtk.Builder() builder.add_objects_from_file(ui_file_path, ["send_stack"]) keys = gpgmh.get_usable_secret_keys() klw = KeyListWidget(keys, builder=builder) klw.connect("key-activated", self.on_key_activated) self.klw = klw stack = builder.get_object("send_stack") stack.add(klw) self.stack = stack # This is a dirty hack :-/ # The problem is that the .ui file contains a few widgets # that we (potentially) want to instantiate separately. # Now that may not necessarily be what Gtk people envisioned # so it's not supported nicely. # The actual problem is that the widgets of our desire are # currently attached to a GtkStack. When our custom widget # code runs, it detaches itself from its parent, i.e. the stack. # We need need to instantiate the widget with key, however. fakekey = gpgmh.Key("","","") kpw = KeyPresentWidget(fakekey, builder=builder) def on_key_activated(self, widget, key): log.info("Activated key %r", key) #### # Start network services self.avahi_offer = AvahiHTTPOffer(key) discovery_data = self.avahi_offer.start() log.info("Use this for discovering the other key: %r", discovery_data) #### # Create and show widget for key kpw = KeyPresentWidget(key, qrcodedata=discovery_data) self.stack.add(kpw) self.stack_saved_visible_child = self.stack.get_visible_child() self.stack.set_visible_child(kpw) log.debug('Setting kpw: %r', kpw) self.kpw = kpw def deactivate(self): #### # Stop network services avahi_offer = self.avahi_offer avahi_offer.stop() self.avahi_offer = None #### # Re-set stack to inital position self.stack.set_visible_child(self.stack_saved_visible_child) self.stack.remove(self.kpw) self.kpw = None self.stack_saved_visible_child = None class App(Gtk.Application): def __init__(self, *args, **kwargs): super(App, self).__init__(*args, **kwargs) self.connect('activate', self.on_activate) self.send_app = None #self.builder = Gtk.Builder.new_from_file('send.ui') def on_activate(self, data=None): ui_file_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "send.ui") self.builder = Gtk.Builder.new_from_file(ui_file_path) window = self.builder.get_object("appwindow") assert window self.headerbar = self.builder.get_object("headerbar") hb = self.builder.get_object("headerbutton") hb.connect("clicked", self.on_headerbutton_clicked) self.headerbutton = hb self.send_app = SendApp(builder=self.builder) self.send_app.klw.connect("key-activated", self.on_key_activated) window.show_all() self.add_window(window) def on_key_activated(self, widget, key): #### # Saving subtitle self.headerbar_subtitle = self.headerbar.get_subtitle() self.headerbar.set_subtitle("Sending {}".format(key.fpr)) #### # Making button clickable self.headerbutton.set_sensitive(True) def on_headerbutton_clicked(self, button): log.info("Headerbutton pressed: %r", button) self.send_app.deactivate() # If we ever defer operations here, it seems that # the order of steps is somewhat important for the # responsiveness of the UI. It seems that shutting down # the HTTPd takes ages to finish and blocks animations. # So we want to do that first, because we can argue # better that going back takes some time rather than having # a half-baked switching animation. # For now, it doesn't matter, because we don't defer. #### # Making button non-clickable self.headerbutton.set_sensitive(False) #### # Restoring subtitle self.headerbar.set_subtitle(self.headerbar_subtitle) if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) app = App() try: GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: pass app.run() gnome-keysign-0.9/keysign/send.ui000066400000000000000000000556741310507150200171120ustar00rootroot00000000000000 False Select and send key True False slide-left-right Keylist True False 10 40 vertical 6 True False True 5 error False 6 end False False 0 False 16 True False You don't have any keys! Please use, e.g. Seahorse to create one. True True 0 True False gtk-dialog-error False True 1 False False 0 True True 0 True False 15 15 vertical True False True False <b>Select a key for signing</b> True 0 False True 0 True False True 0.011764705882352941 <small>Times signed</small> True False True end 1 False True 0 400 True True never in True False 200 300 True False True True 1 False True 1 keylist Keylist True False 5 10 40 vertical 6 True False start <small>To have the key signed, the other person must enter the security code, or scan the QR code</small> True middle 2 False True 6 0 True False True False vertical 6 True False start <b>Key Details</b> True False True 0 True False 6 True False vertical 6 True False end Fingerprint False True 0 True False end UIDs False True 1 False True 0 True False vertical 6 True False 0 0 False True 0 True False 0 0 True True 1 True True 1 False True 1 True False start 10 <b>Security Code</b> True False True 2 True True start F289 F7BA 977D F414 3AE9 FDFB F70A 0290 6C30 1813 True False True 3 False True 0 True False vertical 400 400 True False 20 0 none True False <b>QR Code</b> True 0 True True 1 True True 1 True True 1 page1 page1 1 headerbar True False Select and Send key Keylist True True False vertical gtk-go-back True False True True True True False True 0 gnome-keysign-0.9/keysign/util.py000066400000000000000000000153741310507150200171420ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . from __future__ import unicode_literals import hmac import logging from subprocess import call from string import Template from tempfile import NamedTemporaryFile try: from urllib.parse import urlparse, parse_qs from urllib.parse import ParseResult except ImportError: from urlparse import urlparse, parse_qs from urlparse import ParseResult import requests from .gpgmh import fingerprint_from_keydata from .gpgmh import sign_keydata_and_encrypt log = logging.getLogger(__name__) def mac_generate(key, data): mac = hmac.new(key, data).hexdigest().upper() log.info("MAC of %r is %r", data[:20], mac[:20]) return mac def mac_verify(key, data, mac): computed_mac = mac_generate(key, data) result = hmac.compare_digest(mac.upper(), computed_mac.upper()) log.info("MAC of %r seems to be %r. Expected %r (%r)", data[:20], computed_mac[:20], mac[:20], result) return result def email_file(to, from_=None, subject=None, body=None, ccs=None, bccs=None, files=None, utf8=True): "Calls xdg-email with the appriopriate options" cmd = ['xdg-email'] if utf8: cmd += ['--utf8'] if subject: cmd += ['--subject', subject] if body: cmd += ['--body', body] for cc in ccs or []: cmd += ['--cc', cc] for bcc in bccs or []: cmd += ['--bcc', bcc] for file_ in files or []: cmd += ['--attach', file_] cmd += [to] log.info("Running %s", cmd) retval = call(cmd) return retval SUBJECT = 'Your signed key $fingerprint' BODY = '''Hi $uid, I have just signed your key $fingerprint Thanks for letting me sign your key! -- GNOME Keysign ''' def sign_keydata_and_send(keydata, error_cb=None): """Creates, encrypts, and send signatures for each UID on the key You are supposed to give OpenPGP data which will be passed onto sign_keydata_and_encrypt. For the resulting signatures, emails are created and sent via email_file. Return value: NamedTemporaryFiles used for saving the signatures. If you let them go out of scope they should get deleted. But don't delete too early as the MUA needs to pick them up. """ log = logging.getLogger(__name__ + ':sign_keydata') fingerprint = fingerprint_from_keydata(keydata) # FIXME: We should rather use whatever GnuPG tells us keyid = fingerprint[-8:] # We list() the signatures, because we believe that it's more # acceptable if all key operations are done before we go ahead # and spawn an email client. log.info("About to create signatures for key with fpr %r", fingerprint) for uid, encrypted_key in list(sign_keydata_and_encrypt(keydata, error_cb)): # FIXME: get rid of this redundant assignment log.info("formatting UID: %r", uid) uid_str = b"{}".format(uid).decode('utf-8', 'replace') ctx = { 'uid' : uid_str, 'fingerprint': fingerprint, 'keyid': keyid, } tmpfile = NamedTemporaryFile(prefix='gnome-keysign-', suffix='.asc', delete=True) filename = tmpfile.name log.info('Writing keydata to %s', filename) tmpfile.write(encrypted_key) # Interesting, sometimes it would not write the # whole thing out, so we better flush here tmpfile.flush() # If we close the actual file descriptor to free # resources. Calling tmpfile.close would get the file deleted. tmpfile.file.close() subject = Template(SUBJECT).safe_substitute(ctx) body = Template(BODY).safe_substitute(ctx) email_file (to=uid.email, subject=subject, body=body, files=[filename]) yield tmpfile def format_fingerprint(fpr): """Formats a given fingerprint (160bit, so 20 characters) in the GnuPG typical way """ s = '' for i in range(10): # output 4 chars s += ''.join(fpr[4*i:4*i+4]) # add extra space between the block if i == 4: s += '\n' # except at the end elif i < 9: s += ' ' return s def parse_barcode(barcode_string): """Parses information contained in a barcode It returns a dict with the parsed attributes. We expect the dict to contain at least a 'fingerprint' entry. Others might be added in the future. """ # The string, currently, is of the form # openpgp4fpr:foobar?baz=qux#frag=val # Which urlparse handles perfectly fine. p = urlparse(barcode_string) log.debug("Parsed %r into %r", barcode_string, p) fpr = p.path query = parse_qs(p.query) fragments = parse_qs(p.fragment) rest = {} rest.update(query) rest.update(fragments) # We should probably ensure that we have only one # item for each parameter and flatten them accordingly. rest['fingerprint'] = fpr log.debug('Parsed barcode into %r', rest) return rest FPR_PREFIX = "OPENPGP4FPR:" def strip_fingerprint(input_string): '''Strips a fingerprint of any whitespaces and returns a clean version. It also drops the "OPENPGP4FPR:" prefix from the scanned QR-encoded fingerprints''' # The split removes the whitespaces in the string cleaned = ''.join(input_string.split()) if cleaned.upper().startswith(FPR_PREFIX.upper()): cleaned = cleaned[len(FPR_PREFIX):] log.warning('Cleaned fingerprint to %s', cleaned) return cleaned def download_key_http(address, port): url = ParseResult( scheme='http', # This seems to work well enough with both IPv6 and IPv4 netloc="[[%s]]:%d" % (address, port), path='/', params='', query='', fragment='') log.debug("Starting HTTP request") data = requests.get(url.geturl(), timeout=5).content log.debug("finished downloading %d bytes", len(data)) return data gnome-keysign-0.9/monkeysign/000077500000000000000000000000001310507150200163135ustar00rootroot00000000000000gnome-keysign-0.9/packaging/000077500000000000000000000000001310507150200160545ustar00rootroot00000000000000gnome-keysign-0.9/packaging/PKGBUILD000066400000000000000000000020111310507150200171720ustar00rootroot00000000000000# Maintainer: Ludovico de Nittis # Contributor: Profpatsch pkgname=gnome-keysign pkgver=0.8 pkgrel=1 pkgdesc="An easier way to sign OpenPGP keys over the local network." arch=('any') url="https://github.com/gnome-keysign/gnome-keysign" license=('GPL3') depends=('python2' 'python2-requests' 'python2-gobject' 'python2-qrcode' 'avahi' 'dbus' 'monkeysign-git') makedepends=('python2-setuptools') source=(https://github.com/gnome-keysign/gnome-keysign/archive/${pkgver}.tar.gz "avoid_monkeysign.patch") sha256sums=('141bdb20a84a3b1fb5deb0fc52c5af0e02c601b2cc4923f89409b2a1b8509f88' 'c749fed5028b61c99292416f980decb23f9b90ce96eae05190aace764251575a') prepare() { cd "${pkgname}-${pkgver}" patch -Np1 -i "${srcdir}/avoid_monkeysign.patch" } build() { cd "${pkgname}-${pkgver}" python2 setup.py build } package() { cd "${pkgname}-${pkgver}" python2 setup.py install --root="${pkgdir}" --prefix="/usr" --optimize=1 } gnome-keysign-0.9/packaging/avoid_monkeysign.patch000066400000000000000000000014551310507150200224470ustar00rootroot00000000000000diff -ura gnome-keysign-0.8.orig/setup.py gnome-keysign-0.8.new/setup.py --- gnome-keysign-0.8.orig/setup.py 2017-03-11 12:07:21.394620381 +0100 +++ gnome-keysign-0.8.new/setup.py 2017-03-11 12:08:02.014620656 +0100 @@ -27,14 +27,14 @@ 'keysign.compat', 'keysign.network', ], - py_modules = [ - 'monkeysign.msgfmt', - 'monkeysign.translation', - 'monkeysign.gpg', - ], + #py_modules = [ + # 'monkeysign.msgfmt', + # 'monkeysign.translation', + # 'monkeysign.gpg', + #], package_dir={ - 'keysign': 'keysign', - 'monkeysign': 'monkeysign/monkeysign'}, + 'keysign': 'keysign'}, + #'monkeysign': 'monkeysign'}, #package_data={'keysign': ['data/*']}, data_files=[ ( 'share/applications', gnome-keysign-0.9/packaging/gnome-keysign.spec000066400000000000000000000025541310507150200215120ustar00rootroot00000000000000%global commit c97ba3c96594592a438fe4fc4a034215a79ebe48 %global shortcommit %(c=%{commit}; echo ${c:0:7}) Name: gnome-keysign Version: 0.7 Release: 0.7.git.%{shortcommit}%{?dist} Summary: GNOME OpenGPG key signing helper License: GPLv3+ URL: https://wiki.gnome.org/GnomeKeysign Source0: https://github.com/muelli/geysigning/archive/%{commit}/%{name}-%{version}-%{shortcommit}.tar.gz BuildRequires: python-devel BuildRequires: /usr/bin/desktop-file-validate Requires: python-gobject gtk3 Requires: python-avahi dbus-python Requires: gstreamer1-plugins-bad-free-extras gstreamer1-plugins-good Requires: python-qrcode Requires: python-requests avahi-ui-tools BuildArch: noarch %description OpenGPG key signing helper %prep %setup -qn geysigning-%{commit} %build %{__python} setup.py build %install %{__python} setup.py install -O1 --skip-build --root %{buildroot} %check desktop-file-validate %{buildroot}%{_datadir}/applications/%{name}.desktop %files %doc README.rst %license COPYING %{_bindir}/%{name} %{_bindir}/gks-qrcode %{_datadir}/applications/%{name}.desktop %{_datadir}/icons/hicolor/scalable/apps/%{name}.svg %{python_sitelib}/keysign/ %{python_sitelib}/gnome_keysign-*.egg-info/ %changelog * Tue Feb 24 2015 Igor Gnatenko - 0.1-0.git.55f95bd - Initial package gnome-keysign-0.9/requirements.txt000066400000000000000000000003401310507150200174110ustar00rootroot00000000000000# Debian packages #monkeysign==1.1 # FIXME: gobject? # Python packages requests qrcode # Additional Development packages pytest pytest-xdist pylint pep8 # Distribution packages # python2 # python2-gobject # avahi # dbus gnome-keysign-0.9/setup.py000066400000000000000000000070221310507150200156430ustar00rootroot00000000000000#!/usr/bin/env python # from setuptools import setup from setuptools.command.install import install #import py2exe import os import sys # Just in case we're attempting to execute this setup.py # when cwd != thisdir... os.chdir(os.path.dirname(os.path.realpath(__file__))) with open(os.path.join('keysign', '_version.py')) as f: # This should define __version__ exec(f.read()) setup( name = 'gnome-keysign', version = __version__, description = 'OpenPGP key signing helper', author = 'Tobias Mueller', author_email = 'tobiasmue@gnome.org', url = 'http://wiki.gnome.org/GnomeKeysign', packages = [ 'keysign', 'keysign.compat', 'keysign.network', ], py_modules = [ 'monkeysign.msgfmt', 'monkeysign.translation', 'monkeysign.gpg', ], package_dir={ 'keysign': 'keysign', 'monkeysign': 'monkeysign/monkeysign' }, package_data={'keysign': ['*.ui']}, include_package_data = True, data_files=[ ( 'share/applications', ['data/gnome-keysign.desktop']), ( 'share/appdata', ['data/gnome-keysign.appdata.xml']), ( 'share/icons/hicolor/scalable/apps', ['data/gnome-keysign.svg']), ], #scripts = ['gnome-keysign.py'], install_requires=[ # Note that the dependency on <= 2.2 is only # to not confuse Ubuntu 14.04's pip as that # seems incompatible with a newer requests library. # https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1306991 # 'requests<=2.2', # But this version seems to be requiring an old pyopenssl # with SSLv3 support which doesn't work with Ubuntu's 16.04. # So let's require a more modern requests. 'requests>=2.6', 'qrcode', #'monkeysign', # Apparently not in the cheeseshop # avahi # Also no entry in the cheeseshop # dbus # dbus-python is in the cheeseshop but not pip-able ], license='GPLv3+', long_description=open('README.rst').read(), entry_points = { #'console_scripts': [ # 'keysign = keysign.main' #], 'gui_scripts': [ 'gnome-keysign = keysign:main', 'gks-qrcode = keysign.GPGQRCode:main', ], }, classifiers = [ # Maybe not yet... #'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Information Technology', 'Intended Audience :: Legal Industry', 'Intended Audience :: Telecommunications Industry', 'Programming Language :: Python', 'Programming Language :: Python :: 2', # I think we are only 2.7 compatible 'Programming Language :: Python :: 2.7', # We're still lacking support for 3 #'Programming Language :: Python :: 3', 'License :: OSI Approved :: GNU General Public License (GPL)', 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 'Operating System :: POSIX :: Linux', 'Environment :: X11 Applications :: GTK', 'Topic :: Desktop Environment', 'Natural Language :: English', 'Topic :: Communications :: Email', 'Topic :: Multimedia :: Video :: Capture', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) gnome-keysign-0.9/tests/000077500000000000000000000000001310507150200152725ustar00rootroot00000000000000gnome-keysign-0.9/tests/fixtures/000077500000000000000000000000001310507150200171435ustar00rootroot00000000000000gnome-keysign-0.9/tests/fixtures/alpha.asc000066400000000000000000000050551310507150200207250ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQGiBDbjjp4RBAC2ZbFDX0wmJI8yLDYQdIiZeAuHLmfyHsqXaLGUMZtWiAvn/hNp ctwahmzKm5oXinHUvUkLOQ0s8rOlu15nhw4azc30rTP1LsIkn5zORNnFdgYC6RKy hOeim/63+/yGtdnTm49lVfaCqwsEmBCEkXaeWDGq+ie1b89J89T6n/JquwCgoQkj VeVGG+B/SzJ6+yifdHWQVkcD/RXDyLXX4+WHGP2aet51XlKojWGwsZmc9LPPYhwU /RcUO7ce1QQb0XFlUVFBhY0JQpM/ty/kNi+aGWFzigbQ+HAWZkUvA8+VIAVneN+p +SHhGIyLTXKpAYTq46AwvllZ5Cpvf02Cp/+W1aVyA0qnBWMyeIxXmR9HOi6lxxn5 cjajA/9VZufOXWqCXkBvz4Oy3Q5FbjQQ0/+ty8rDn8OTaiPi41FyUnEi6LO+qyBS 09FjnZj++PkcRcXW99SNxmEJRY7MuNHt5wIvEH2jNEOJ9lszzZFBDbuwsjXHK35+ lPbGEy69xCP26iEafysKKbRXJhE1C+tk8SnK+Gm62sivmK/5arQnQWxmYSBUZXN0 IChkZW1vIGtleSkgPGFsZmFAZXhhbXBsZS5uZXQ+iFgEExECABgDCwoDAxUDAgMW AgECF4ACGQEFAlg/S28ACgkQLXJ8x2hpdzQ3QQCfd6BPvdQcfiZ/4v3PkDXHoUll xFAAoIEDAAuhFt0dcSV4/b2Qe90dMbD2iEYEEBECAAYFAlhAamwACgkQnO4bawWb WY5RDwCeIgi58t2Ebm31GD39lEdn9ZIfjzEAnjLw+ESSEUFwtbW4r9gA5/o1Zlh4 iEYEEBECAAYFAlhAamwACgkQ9woCkGwwGBNRDwCeM7aed2E6hHxjovtOWZMVkc6u oOcAnidWHOPSbamihhOrYkw/IrTe+zxtiF4EEBEIAAYFAlhAamwACgkQHDQZvxv5 jW0COQEApHdOCgih1UcZxTW3L8Eu3Xh6zZRI1XhLrdgbOdj0oqwA/jRz6C3tj4Hd 7wzziNAwqPcGPJ/E/BqduG4H4V46FbyCtClBbHBoYSBUZXN0IChkZW1vIGtleSkg PGFscGhhQGV4YW1wbGUubmV0PohVBBMRAgAVAwsKAwMVAwIDFgIBAheABQJYP0tv AAoJEC1yfMdoaXc0lkYAoIjBvOtkcw4eVdsN9VBEGbU3qpAjAJ4y8BxPPr4w2LAw VicoBp+E5aB0/ohGBBARAgAGBQJYQGpsAAoJEJzuG2sFm1mOoGEAnjD8NM3U1ibI yWKVyX6ANUcO2ME3AKCrRwIILDPDNa5bze3BWlMBGp23QIhGBBARAgAGBQJYQGps AAoJEPcKApBsMBgToGEAnRZDWRCfiz6EqOlIrcmRISDetPKRAJ4siqfCWJZ4VYq/ 6U7c3cZcuqRtP4heBBARCAAGBQJYQGptAAoJEBw0Gb8b+Y1t06MA/08rlWmL2iqm 9i7192n5QMOu/Fy7sNUaH3A7fC9nyZABAP4ttDIlERolAQUdjO7xLvHx08BFXUup rDxc/sJ9d0fZBrQQQWxpY2UgKGRlbW8ga2V5KYhVBBMRAgAVAwsKAwMVAwIDFgIB AheABQJYP0tvAAoJEC1yfMdoaXc0JqoAnjXKojVPqmldCH7qJR2sh2v0RdN2AKCL 27dGMen8UBnQCM+FLM07FjRDxohGBBARAgAGBQJYQGpsAAoJEJzuG2sFm1mOKy0A nAviX6eMrwaUwfp0OAc9Bonh0fVcAJoDtHqJwCBQm9ZfoCEQQxA66DYUlohGBBAR AgAGBQJYQGpsAAoJEPcKApBsMBgTKy0AnRYbLSL7EqsgWPdZFbWOEw7JVKtAAJ9W 0zSR5aca8T+pZjpovDpB59ThfYheBBARCAAGBQJYQGptAAoJEBw0Gb8b+Y1tU4gA /RgIwm/ajs0PlmyKesW2z26V9f1avw1zJNtJjcdC5dwuAP4ky7zvUgr9V8OlYDUB 7MPATjiLPknFvDn1X1OWfgPicrkBDQQ2448PEAQAnI3XH1f0uyN9fZnw72zsHMw7 06g7EW29nD4UDQG4OzRZViSrUa5n39eI7QrfTO+1meVvs0y8F/PvFst5jH68rPLn GSrXz4sTl1T4cop1FBkquvCAKwPLy0lE7jjtCyItOSwIOo8xoTfY4JEEXmcqsbm+ KHv9yYSF/YK4Cf7bIzcAAwcD/Rnl5jKxoucDA96pD2829TKsLFQSau+Xiy8bvOSS DdlyABsOkNBSaeKO3eAQEKgDM7dzjVNTnAlpQ0EQ8Y9Z8pxOWYEQYlaMrnRBC4DZ 2IadzEhLlIOz5BVp/jfhrr8oVVBwKZXsrz9PZLz+e4Yn+siUUvlei9boD9L2ZgSO HakPiEYEGBECAAYFAjbjjw8ACgkQLXJ8x2hpdzQgqQCfcDXmD8uNVdKg/C9vqI3J SndqknsAnRxzVeHi/iJ73OCKtvFrHbV9Gogq =TFxT -----END PGP PUBLIC KEY BLOCK----- gnome-keysign-0.9/tests/fixtures/pubkey-1.asc000066400000000000000000000027211310507150200212720ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2.1.0-gitb3c71eb (GNU/Linux) mQGiBDo41NoRBADSfQazKGYf8nokq6zUKH/6INtV6MypSzSGmX2XErnARkIIPPYj cQRQ8zCbGV7ZU2ezVbzhFLUSJveE8PZUzzCrLp1O2NSyBTRcR5HVSXW95nJfY8eV pOvZRAKul0BVLh81kYTsrfzaaCjh9VWNP26LoeN2r+PjZyktXe7gM3C4SwCgoTxK WUVi9HoT2HCLY7p7oig5hEcEALdCJal0UYomX3nJapIVLVZg3vkidr1RICYMb2vz 58i17h8sxEtobD1vdIKNejulntaRAXs4n0tDYD9z7pRlwG1CLz1R9WxYzeOOqUDr fnVXdmU8L/oVWABat8v1V7QQhjMMf+41fuzVwDMMGqjVPLhu4X6wp3A8uyM3YDnQ VMN1A/4n2G5gHoOvjqxn8Ch5tBAdMGfO8gH4RjQOwzm2R1wPQss/yzUN1+tlMZGX K2dQ2FCWC/hDUSNaEQRlI15wxxBNZ2RQwlzE2A8v113DpvyzOtv0QO95gJ1teCXC 7j/BN9asgHaBBc39JLO/TcpuI7Hf8PQ5VcP2F0UE3lczGhXbLLRESm9lIFJhbmRv bSBIYWNrZXIgKHRlc3Qga2V5IHdpdGggcGFzc3BocmFzZSAiYWJjIikgPGpvZUBl eGFtcGxlLmNvbT6IYgQTEQIAIgUCTbdXqQIbIwYLCQgHAwIGFQgCCQoLBBYCAwEC HgECF4AACgkQr4IkT5zZ/VUcCACfQvSPi//9/gBv8SVrK6O4DiyD+jAAn3LEnfF1 4j6MjwlqXTqol2VgQn1yuQENBDo41N0QBACedJb7Qhm50JSPe1V+rSZKLHT5nc3l 2k1n7//wNsJkgDW2J7snIRjGtSzeNxMPh+hVzFidzAf3sbOlARQoBrMPPKpnJWtm 6LEDf2lSwO36l0/bo6qDRmiFRJoHWytTJEjxVwRclVt4bXqHfNw9FKhZZbcKeAN2 oHgmBVSU6edHdwADBQP+OGAkEG4PcfSb8x191R+wkV/q2hA5Ay9z289Dx2rO28CO 4M2fhhcjSmgr6x0DsrkfESCiG47UGJ169eu+QqJwk3HiF4crGN9rE5+VelBVFtrd MWkX2rPLGQWyw8iCZKbeH8g/ujmkaLovSmalzDcLe4v1xSLaP7Fnfzit0iIGZAGI RgQYEQIABgUCOjjU3QAKCRCvgiRPnNn9VVSaAJ9+rj1lIQnRl20i8Rom2Hwbe3re 9QCfSYFnkZUw0yKF2DfCfqrDzdGAsbaIRgQYEQIABgUCOjjU3gAKCRCvgiRPnNn9 Ve4iAJ9FrGMlFR7s+GWf1scTeeyrthKrPQCfSpc/Yps72aFI7hPfyIa9MuerVZ4= =QRit -----END PGP PUBLIC KEY BLOCK----- gnome-keysign-0.9/tests/fixtures/pubkey-2-uids.asc000066400000000000000000000024001310507150200222270ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mI0EWD/sgQEEAM91KvxbtIlraZtZDCFtiKIBceR2aY5KmnwdNZdVrmndw8otwf6g Pk3/K9hZhTllbpRyY6A7dpLr2g4/uuTMLsM32WRl3xRsHGjoHxfyw3nc3HDcpx7y 3k2JN/cf0a4L1+DE82yJt3fUqSUwepb6+YDbg+xLsmzdewDmf9Bnlu5bABEBAAG0 GVRlc3QgS2V5IDx0ZXN0QHRlc3QudGVzdD6IuAQTAQIAIgUCWD/sgQIbAwYLCQgH AwIGFQgCCQoLBBYCAwECHgECF4AACgkQA0CU+4/Ulfu5pQP+MFCmUrs9Q+XCcRpO 1xLNZtvwDegX7HHYlrAp8Xv/m8n21TE1svrO0NR62OXi/OROxLnpd72/ZD2/BiuV ittsPWup+QK9dddg0TrVDakvaColXmRXhSKzyX4YhaX1F2hbyhzBdlI58OuIsGjM fDsMhAshRf7zdiLEwB+ivzu9gpe0GUFub3RoZXIgVGVzdCA8c2Vjb25kQHVpZD6I uAQTAQIAIgUCWD/uOgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQA0CU +4/UlftBiQP/bu97IEArTem1q2FVks4Yjy7dK1Izfa1CV7JeUS9anoTdeBRUo0T9 VoyI31qUbuWwL/dmSKJ89UZvwwumqDfRR5nr6NMOMec5rnrk9kjnvqa+Tlvj0cQG +01EVYWoBkdlYD7Dut/+ZjCOqHQVHORpKgzdtWDwrDyiWgWhc3PAzPi4jQRYP+yB AQQAxFhAuUVYPVGRNX7m5gjgBYXx/qtNAmdyCv0ivFxAWoqcXuy4LjGFpCKTUDHy XOpv3czfIW7VVh4w3xoXD9OFg2ySU5Lt51/i2PYZsa2RNbogUjwD0VCzqIwXATzp eaA3CwDD+0zpXHhrTJv4FihrYiPnhBjGhg4F6PxtJQyYZYMAEQEAAYifBBgBAgAJ BQJYP+yBAhsMAAoJEANAlPuP1JX7iqID/ijG4Nol51psx7cL9LWLhhNy3rB8gDUr e7NLvrnjf2OXOXrzgqaPq59LHwI0Q+d2Qh2AjAjMgOrpN7CRP6lL9GdKpWm2U5F3 8esFxuMPUDmNyWE5/iiR+IVYI67ZPQrwARGjlMjlTnY345t7FqfS8bTqE2XXNxXK Zezw7yly8itB =9BoK -----END PGP PUBLIC KEY BLOCK----- gnome-keysign-0.9/tests/fixtures/seckey-1.asc000066400000000000000000000032431310507150200212560ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v2.1.0-gitb3c71eb (GNU/Linux) lQHpBDo41NoRBADSfQazKGYf8nokq6zUKH/6INtV6MypSzSGmX2XErnARkIIPPYj cQRQ8zCbGV7ZU2ezVbzhFLUSJveE8PZUzzCrLp1O2NSyBTRcR5HVSXW95nJfY8eV pOvZRAKul0BVLh81kYTsrfzaaCjh9VWNP26LoeN2r+PjZyktXe7gM3C4SwCgoTxK WUVi9HoT2HCLY7p7oig5hEcEALdCJal0UYomX3nJapIVLVZg3vkidr1RICYMb2vz 58i17h8sxEtobD1vdIKNejulntaRAXs4n0tDYD9z7pRlwG1CLz1R9WxYzeOOqUDr fnVXdmU8L/oVWABat8v1V7QQhjMMf+41fuzVwDMMGqjVPLhu4X6wp3A8uyM3YDnQ VMN1A/4n2G5gHoOvjqxn8Ch5tBAdMGfO8gH4RjQOwzm2R1wPQss/yzUN1+tlMZGX K2dQ2FCWC/hDUSNaEQRlI15wxxBNZ2RQwlzE2A8v113DpvyzOtv0QO95gJ1teCXC 7j/BN9asgHaBBc39JLO/TcpuI7Hf8PQ5VcP2F0UE3lczGhXbLP4HAwL0A7A1a/jY 6s5JxysLUpKA31U2SrKxePmkmzYSuAiValUVdfkmLRrLSwmNJSy5NcrBHGimja1O fUUmPTg465j1+vD/tERKb2UgUmFuZG9tIEhhY2tlciAodGVzdCBrZXkgd2l0aCBw YXNzcGhyYXNlICJhYmMiKSA8am9lQGV4YW1wbGUuY29tPohiBBMRAgAiBQJNt1ep AhsjBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCvgiRPnNn9VRwIAJ9C9I+L //3+AG/xJWsro7gOLIP6MACfcsSd8XXiPoyPCWpdOqiXZWBCfXKdAWAEOjjU3RAE AJ50lvtCGbnQlI97VX6tJkosdPmdzeXaTWfv//A2wmSANbYnuychGMa1LN43Ew+H 6FXMWJ3MB/exs6UBFCgGsw88qmcla2bosQN/aVLA7fqXT9ujqoNGaIVEmgdbK1Mk SPFXBFyVW3hteod83D0UqFlltwp4A3ageCYFVJTp50d3AAMFA/44YCQQbg9x9Jvz HX3VH7CRX+raEDkDL3Pbz0PHas7bwI7gzZ+GFyNKaCvrHQOyuR8RIKIbjtQYnXr1 675ConCTceIXhysY32sTn5V6UFUW2t0xaRfas8sZBbLDyIJkpt4fyD+6OaRoui9K ZqXMNwt7i/XFIto/sWd/OK3SIgZkAf4HAwIoimqPHVJZM85dNw6JtvLKFvvmkm3X uoCUG5nU6cgk6vetUYiykuKpU4zG3mDtdZdIZf76hJJ6lZTSHH9frLy7bRYPfu/k U1AFd1T1OxENiEYEGBECAAYFAjo41N0ACgkQr4IkT5zZ/VVUmgCffq49ZSEJ0Zdt IvEaJth8G3t63vUAn0mBZ5GVMNMihdg3wn6qw83RgLG2iEYEGBECAAYFAjo41N4A CgkQr4IkT5zZ/VXuIgCfRaxjJRUe7Phln9bHE3nsq7YSqz0An0qXP2KbO9mhSO4T 38iGvTLnq1We =m0YJ -----END PGP PRIVATE KEY BLOCK----- gnome-keysign-0.9/tests/fixtures/seckey-2.asc000066400000000000000000000065671310507150200212730ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1 lQOYBFgvBu4BCAC9mzZbSXvu1tQYX2lfMsySyMouIwzP7t3pebW1R2Mqx7GvuivH oE69sve56f+kfK4TKRfPSTq4KsbNApTkXfxU7pvVwgDq85mVzIFSBWWkiRMzPzix 1bVpZAfvNsHea+Cm/4h3C4GHZj4PC4yw7r/XesShqgR34nNUYwMlzFa4zAyRUd2N B7YJR6CkEiwOVeV3SVZuPOxx2a49SLaOFMhKg1duAJq7bZIuRa65rkFlSlYHL5nP Wo/3HG0ow/jY1Tk1UnOhv5s2VVQn9uIFoO8WoYhRrHeUnt0kiPfl7CHngBhPTVZH auXdQ7Pd+3GNjHuNnXeaXvD9zeD4zD5qCP0xABEBAAEAB/oDEEMLrL9hLgmfDsrL wq0PtOMGmM/IFX9ymmIg68zcSZeDRw18oohZkOhnK0xtBdixyMJ9bNAs+DMrGwVF 2IdPTGWqYzT5ltyet39InUhPPsNXjiHZebvLJQmELqacp372cJi6kUe5mAgDqgRo oKSErXnU20OxBJcn2cuyUvL89fchfIDhitwc8dDlhMHRo0pOuZgDKMeNSlSHw2lE YVb56bng0oYDhHFp8WcYndsOlxvC+6zvCT826nfPse2yVMYE5YgexQFZBRQuvKL3 GpN3w0Ymlry+YHud4h/9NcnTvJCoRx7K5jybZFzS3p/bukRedcYOoHIzRUDfdJaX 0Qr5BADWKI7WOl6HTLgRWg2h1dxEM+lV12HeYjxy4qwD4/iIVo4pRUlul+e/C/PC zt0Qht+0CGSOUmKkA43IXqH8YELILN1jNDHphDpKIGtAn8oe71sdMVNOg+L4xpDE EI26WM3+zI5YXwfXE61pjr5Ml65mJMuc4EtvD7k/NJaIrU0mFQQA4qak6cTJOgBk RlFIIIYy53FI9BoDO5d84Rs8l0A96dM/O86J25k43WmJ4S2s7ufOIPhH4sXdJYYF E1TSLuNEoYNhTuw0oB0aMUNswdUQJC4O9JN6ObHu7eBdbycpZtN6eNnC0ZzOOoPS iHuEh4mmQxJIjffFzJsSQI1vsk2Wfa0D/i4UNDw96C5EpM+Rh9wcjNb48IIaTpzN lTKyH1+VRp+KCDp6B/VMZ+de5Yo3TlyWzcB4BvcgrkzoTPW74SIbLMudkNcWdOTP YksTPMBkkYWSpOzPQ+1l7f1Ps5g/29htcCcYnRWF3xG4SuJW8GPs7iM+GQ9u6Xek Sgm6Lw1EOxtcQ7K0E1Rlc3QgS2V5IDx0ZXN0QGtleT6JATgEEwECACIFAlgvBu4C GwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEKXIm4s4PudUlvEIALei+uSZ n8a230B+CUfI6z+t9HlCjjR5FAlyYlUWIDRmox6ny/BGAsa+eJo2fgpAfYsiTAJv iY08oSol5NH52PwCrXOh8mMpttvWLuokz0FsrCocWoerRIErLcx9ytibkyQSNkdH HeDRmDpeNXJyD9c6sfy5vSCx0J+P+roNBKMY/X2vLouL4RoEM+eMKHQxENzKxsj9 BzIgGkdYQjlmSzQo3TcyIUm8Zl4u9Ag3dVQzS7hZKaIoWsfzeLMu3v+oGLhCv9mk e9Xw19usdJ/QSA9P0B+pqHTX+7DO9aUWWX7r0uvtioB7e2Xt7q+9Fmnmwb5AR9g8 OrkeWdftPwjvDf+dA5gEWC8G7gEIAMUKJ5elmdU9jZGu8IM78z2x5cxaFDKBsi9S Xmpg5DV00ru0ZDW0IGvjQO4PO3Rtc/EJJPbroYjcAyBl2RYKXor2Rpj0V4ciGWEc nJEMnoyaeNsrjHlxA2FpJUOySEfLMtXSQyujFM/QqO85TdC51tWeloBJBHYNrxp3 jUlRf2tYcOSyqMEgORiKh6K73u6UDPUTH/VjJR3/fdVeedQhPtUi2uPEKjLEtXY6 +ZmK3vaIRMkDYzVjNEGXnlUlo9eKudJDCqyW5JRLuErK3VwdwSQ2q7nR/mx6pK5R 1cbMl62uPz3Ia7t+6yRFmXVM5E2Fr7PNjYpOLwIE9h8A7ISIWuEAEQEAAQAH/i9X rcSjfu8772ByEorNpDeOH2M2v4SVIf15woK3zg1ECQAdqzg2E9YoT6kDUus6AzIG WvHTEshh6IRnxD2l9ypXWwYUK1WmLUfmin3VdODemqw6bfGd5EyK6W+3DePmIEw8 zXPWJsF55qU39QjyJfrEtG+VW7OLvSdUU0erA70EIk4rwRB1Sl3uDI477sYLMNY1 SdxlDAWcLhnLrIbfNiTyZHOUmGHGIUk2EgEeAJSqmb27aY5KJ/eE7AJxHQowoqFZ IQLMdmP3oxR93gbnFtyo1QhUC2o4aUWLz8N0JM1DWxEcBbSS6dqxngEjsD2y3D3C qhajricmhLfKcgjF2zkEANI6edlwfD4DuU5DF1yqo0+iSzNsops+W02TBhfr1kvD 3AO2mMqKewPGGdy216qQReoDSQ9y6RzidD3VzXOHtChGPHOmeF3uFu9x5+ySU8gx xBVjth6ULxxgRZ3mU/FNao5rpKtSJD8AOZAmJKz+fYbXp+6uuZAajYaVfGP4C4TN BADv8JNcWJfGMRtZzYkyLDzHN74YWoAzVw7iMuNQgtNJCPXa5Eky5O2u71dWJEZd dp6X7ZCGNjgleORanUn4xWLAoRM5cDVhjPMYVBRECfa/6VLuPQ4AsnmAOb2bmsr1 N/vTq2eOjlzV+lrbp1wF1KKRJGnW/X9kRiG0FyKE76DOZQP/XmkLxGioIsmPqi+P +/zWbY4Ik8sNmpBrmrzrGLzsRZ9CFj0DLcek6neCRKv5rG8oGfQvo8wlMToTF60C hYBzgefuDyOhfRsnpXSc3gD4HRQ85yExkZ7unyrRIR+3hDf9i9PcLMP/y12trFIr 3X5KCkGjHi6eD/pIr2CYiTKuGCkzwokBHwQYAQIACQUCWC8G7gIbDAAKCRClyJuL OD7nVJ3nB/9jF3kHmCWqE9JWWKwNhWcWEs6Jd3GfzZDWs4I0gWWfdMYjhC05kl6m keH6PNwMr0qHDcKJ0GCJF6Tu1uRfhxT5/mqcwkG739bQzwhfLtdg1QpaLV5L3nPO /NWUiWlYyqTtPNoluRGfyAwX0JvWdHSJxU78lziMbc4HIv8qJ2Kcsm4CU1hCJFnc bD9DRBK+aHWJDlwW+kjNQZeNLM1uznTufd+CqkHOcSzxn2zEvQIkN6O8nrQ9pq+7 b/ez3zr3GWuLZxpxBvZ6TC35nmdutqwKKhJn939k+XbWKpG+BF2rhdI3pTzcpjKC pRk+jFP4mkML3OgpUm6we3x2f14e/KoP =nbTi -----END PGP PRIVATE KEY BLOCK----- gnome-keysign-0.9/tests/fixtures/seckey-latin1.asc000066400000000000000000000040661310507150200223120ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1 lQHYBFkQq/4BBACgTJrZpB0KioOfLrFmRg3DFtrbtN4Ww/mDBgotuWp2xTLTWpiY zuIaEFWj941ZM4Z1PrlTSadEd+xw6RXjW01uPQErNpViZF9jz3NmAsWrqu+2uemu vzq5BXW4qu4MhsxN1qn+CJx7odfQQZKOwEOwOXN2O/RokYKD5FizH71LdQARAQAB AAP9EkK/xijPpR6D4XueemXjFfUR0AMB5MqE70PeH3jWk7qhsFmK9idlMu5m7yOZ fs6jk9ImJwKGM0LNFMy8sVZ6krTnBG9VL2eIN+1CkEgB66ywiTEG5uT/Rkhkb00C scqedBS5CzVxbG8lZkn54Pj5vkPF6+eJ73frTS1NY79JDFkCAMaAk5RID526/ed8 bL3wqaDTk64aht79CdVWWiz2+fdxS6Pj9T/y5/ijkhjuRQcakG/TDLRACBahF6/M H+Zvyj0CAM67Lqbmvtd90JMA0nNDJBXqYuRN00/XHRKo98uswYFHl/h+WoOqoqtC M3btr/SCVGnbZuZTFmkBmbi51gpS8ZkCAJHXIivzI02+sAu3hwOBmQTHUOQZjB8b IknYpkPXnWUY4MF1GRuTOCftBfIeIoFJJYEkaEQE4Wv8JOiGbXi+SZCkHLQTZm/2 ZeliYSA8Zm9vQGJtYS5kPoi4BBMBAgAiBQJZEKv+AhsvBgsJCAcDAgYVCAIJCgsE FgIDAQIeAQIXgAAKCRC2Bzw4kirJYDPhBACbjasupevAxy8eLJ1nsQBfvkv6e4pl K6VUjq3tjl2E0SZ9woZBsYdH2hnW8s7z5fS3Dk1Pw+tSm+mPt9vy24T49Mv8NZtC APiIE27eHugbla5TKzFSigN/hkjivXG7bxSAIfit7DN6KPQWVE9pfgpIwosc9U20 MfPRq8+5VGL5QJ0B2ARZEKv+AQQAnfzUGtWRz6JaBiLof/3eNXspSep28UPq4Yrb ENCRdz54swX1BapJCEZZFdFzu3dBWvFeGJLCGziZd09yFLbXHFM8K4wx2yIQDXKC 0OjowLoQT8YEZQvqKEmX8RyqhV5qyhwDf3ghyqbm71qXj1LaAM9RIts2UAOS8bUW 3uviwNsAEQEAAQAD/0zFurCbjfKvOz32IrNnw16LzgGcTVZqoa4eUtv17mpa0j50 q5+oIztBLDM9CBdWGU0/M3GPh4HA3FqtIYvNWfQnN6QtyzaM0fyHcpbrha7j9jK/ 3JqoEzP9oaPVwOWAjXPs81PsiqyHzFL0ZA8wvKw9xhDH9Lo/YhNWaGV+tG4ZAgDD PGbxIIa10ZGaH9mvd7urFbVB/WwzUUecnU9pw5/F+yP8dE5Yjn7Av3MJIEa8Qtwn gKeoRvxaKhD9JrbRtBY3AgDPKKHYE4WBVzUKDA0ctQ/RGS0SbNzyf4YdtHfrgLG0 KTp4Jh1PRUhsMkBF/rfsFIr9PpApvBzAFWB2Hx4yt1h9Af4pbG8nlGP/H2V8rY1U 8MYMGI7l1FMrWd6Xz0gmhbCuNzFBKqCPLNIWqE7LMU3BbC7cfbozXujQKX/2eawe j3E0nbOJAT0EGAECAAkFAlkQq/4CGy4AqAkQtgc8OJIqyWCdIAQZAQIABgUCWRCr /gAKCRCW6kaJ32540A7AA/9VzXQmMzf26FWTaLHSf1uqBwIY29/PRGrfJvj5yu63 Sw0+agLEkorkD9IKsxVcnWpCI17Xw3hNzSus5euqcdujcy5An0+eEEjxXw13GMn0 zyVJbmBUngTdkHZuY3jTTj92GSv8jo+GZW+uWp7ZDPqpTZoWeH5d1qPq9Hd3LleT p801BACH/NIHLPFAUELnrwTeIZhG8Pa5teqWtYulZtJsWxIyRszLmjvciaTraImE v00K8SAqtMX8+sA7IGBw9TUSmNFCNvhSQZdYGcyfJsN5/X0o5a/R/OTzfrVK/YU9 0fEDu4pTpOe6pmiTUr0dgtKX02MpwfNcKsp2scC5F93LwYQkWQ== =upUO -----END PGP PRIVATE KEY BLOCK----- gnome-keysign-0.9/tests/fixtures/seckey-no-pw-1.asc000066400000000000000000000042101310507150200223070ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1 lQHYBFg/7IEBBADPdSr8W7SJa2mbWQwhbYiiAXHkdmmOSpp8HTWXVa5p3cPKLcH+ oD5N/yvYWYU5ZW6UcmOgO3aS69oOP7rkzC7DN9lkZd8UbBxo6B8X8sN53Nxw3Kce 8t5NiTf3H9GuC9fgxPNsibd31KklMHqW+vmA24PsS7Js3XsA5n/QZ5buWwARAQAB AAP8DnLLlp3Qb2MCt5C2bpGRWtnHk9tyCI3w6m9NBIgxcyrAcGagGdq73B9xxJ9K JFrdyQrvyKtmMQn/ZXn8yziJPRad12X3//rj6XChGf8aY7X036UTXkKSekjboymh KTNQiH/mqLg1A64lT0lXURt6X4udbuQdI9Kg2ayZR8jfZhkCAOIyzjqpBQCH0pKo ggoEcHwTWxyERPzD0D0+jk1k3Lv2HpNVr0+o9fyeGUAyxhXbPiIxkvM6/2To4HwA cqAk8RcCAOrKRpxRAOslqnnjsiWZBSIo2REzLDA0pxmmXLE0ESskHraNYg4xXL4h qbGUKaeoQI5N2jPzvSCp0Nc3KSktD10CALYofTskvQqX3CcoahiwB1pDktZeBvcZ Ik0k0K5JXQYaTtWnzuGvNNu6Psu/JSU8Q1JkbcJUNX94QCpKuTWOQ7qWrbQZVGVz dCBLZXkgPHRlc3RAdGVzdC50ZXN0Poi4BBMBAgAiBQJYP+yBAhsDBgsJCAcDAgYV CAIJCgsEFgIDAQIeAQIXgAAKCRADQJT7j9SV+7mlA/4wUKZSuz1D5cJxGk7XEs1m 2/AN6BfscdiWsCnxe/+byfbVMTWy+s7Q1HrY5eL85E7Euel3vb9kPb8GK5WK22w9 a6n5Ar1112DROtUNqS9oKiVeZFeFIrPJfhiFpfUXaFvKHMF2Ujnw64iwaMx8OwyE CyFF/vN2IsTAH6K/O72Cl7QZQW5vdGhlciBUZXN0IDxzZWNvbmRAdWlkPoi4BBMB AgAiBQJYP+46AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRADQJT7j9SV +0GJA/9u73sgQCtN6bWrYVWSzhiPLt0rUjN9rUJXsl5RL1qehN14FFSjRP1WjIjf WpRu5bAv92ZIonz1Rm/DC6aoN9FHmevo0w4x5zmueuT2SOe+pr5OW+PRxAb7TURV hagGR2VgPsO63/5mMI6odBUc5GkqDN21YPCsPKJaBaFzc8DM+J0B2ARYP+yBAQQA xFhAuUVYPVGRNX7m5gjgBYXx/qtNAmdyCv0ivFxAWoqcXuy4LjGFpCKTUDHyXOpv 3czfIW7VVh4w3xoXD9OFg2ySU5Lt51/i2PYZsa2RNbogUjwD0VCzqIwXATzpeaA3 CwDD+0zpXHhrTJv4FihrYiPnhBjGhg4F6PxtJQyYZYMAEQEAAQAD/RRHKxQXYdgf Yvhb7Vvnob9gSJBtP6xWY7RX1W0PuAPB2gmBuDnpGmzLt1wqdGX9PmVxYcARss3M m26HQsd7KIgub8bi9iVo2/Vr+XRyAziK4BtkdX6qVLbpYYM28ZVbMmG3WqehS/Ot +dtg3y23ZPvcwoZBT+StiZdpQGtram9BAgDdkBqg1NRqeBTKKO8j52HE3xoTXSbA /lduSDXdVfdEBA2X0SJAD+y0z/PvsYj01MZy7JRuAEeqlcM3P9y/6TfDAgDi3Lp1 gpphjk8ANCSwEMwH1rh8u/Sa46ir3v9mR0Rgtb6nZedeJS0zGwCE/rlH1aXuk/vB 6n8pY75obJclcv9BAgDPa+bOYX329PTfUY86ey+z5FLFDHVMHRFGt+ibXba3FKy7 7NJCfNxIbCJBzW6VX77HnEJIj6D3DiOawkZTKfswqLaInwQYAQIACQUCWD/sgQIb DAAKCRADQJT7j9SV+4qiA/4oxuDaJedabMe3C/S1i4YTct6wfIA1K3uzS765439j lzl684Kmj6ufSx8CNEPndkIdgIwIzIDq6TewkT+pS/RnSqVptlORd/HrBcbjD1A5 jclhOf4okfiFWCOu2T0K8AERo5TI5U52N+Obexan0vG06hNl1zcVymXs8O8pcvIr QQ== =p3BI -----END PGP PRIVATE KEY BLOCK----- gnome-keysign-0.9/tests/fixtures/seckey-no-pw-2.asc000066400000000000000000000035731310507150200223230ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1 lQHYBFhAJUwBBACgTyIFbzX0mNDO8JQe/Zl9ZH6r+L1/EtBvu3ULqlzQ6aopJlDy ibxfpZ1B2RAv+TjriDSdv9TxLdXmWlaq3qbG1q/DcS1iXKRTk3TBZI40gVZi3JFJ e1D4kIKV/rl3z6PuhVUn4zFkNkX7ZL53JSkdiDdU+7bDHuClLxBsbmRMuwARAQAB AAP8C2a1X7eSGcxIhX88uZuFsBJWo/pz17bJ1jh50ZOTOFR2Aqkz9pvvJspLjeRX L2JFNxMf0txS07hTzyc7pLljGQJY+UdboLtmXMZaQUVqBqXPzup01YPm4pqWPyTR +XjU+iROt0G8nupSYDd3ozyuM4n6LtjiRpDBPiZHjmgpWvkCAMogNy+BfD0fvv8J Z1NDZKupes6GFAGvWEX0Ea1S7NUkE5DIR1oqc17I9kr+/SVDwearQkTULnNRrDhL jD7NOw8CAMsJm/JPyWV8VQxtIUtipTkB8YrKIC3ekFZfoG9v+Ua1hG9CCjKVbqzo CXK7iczY/O66/NndZKpAN4xl0d3EQ5UCAKwiyAE0tqmjjS7brUOP8r8Dh2ND4GMU oQbrFeVanxbH8yH8x+ZwRLs1g8NTkjxR7fJqrPG8CV4nbdiEVrQvdOSiD7QqVGVz dCBLZXkgdy9vIFBhc3NwaHJhc2UgPHRlc3RAZXhhbXBsZS5jb20+iLgEEwECACIF AlhAJUwCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJENiMrIUqxuEPZisD /RXmvebrYuQ7rxx4YI+Lf9u71Ilxj2n2lLEbybKDUKURx+BOL8ETgMz3VAmjOMru xkG3UhVcFohzBZTC3/ixunhX6SYa9YI8VHgN1oWBzu1r8wraQdbGoxe/zX5kCkl5 2nC0PetY7fW/+ZG4xLVQIIfz3jq8F0JeSj+oZ6iP2kI5nQHYBFhAJUwBBADID0GX S12vcyB/xmtlCaSQixTp0SNo6FX45Q/u/f7PECJC42tByBP49lBSw/ahOeIfej1D 3u36lG/ekRcXdljtUPwJY+kHu4cjK0gx2LR5rgq+kC7djBp47IV/o/aW1E5z3swu LOmShyQ2Mk2hXWcx50mjIvTcy9u+ve9oxZ6S4QARAQABAAP+KUfUnfZkL3bPBu6X bHLP7S91skWAS/5c9w20+viYInvOxgSNNjalwGJ68okTE/OQsDQV/jI64tDMQJ2p qSK3eGsotDDS3ej6e8NOBUPuGkqkD3KPv4NryPqo8dsIlIgnjobxE0h3MGL2BuN0 36SOdpo5HIGkRK9P/2hN1BnkfY0CANz+qKbaxmF2xfdyBAHRguMO2O965fyfJeHA GK8Zs2C9DKRXBVNvS6QJIY4EMlwDk2EE3byHKg92qQHg305kZQ8CAOe/rRN5KXut awt6/3qhWJMraVEiLiJvaM3QzsEFthk1xARrAinxctKNcaOg96Ofkd2jqCz/Ozcg 8IyJRqoe6Q8B/3MAJntk09cU53o3wABwZszEsxL8xoOfu5Lvoq5pl94LAOoLzUdJ YcT/3hzSc087YtLQQQwcas1bfgiAuqmZWFeiy4ifBBgBAgAJBQJYQCVMAhsMAAoJ ENiMrIUqxuEPj2MD/1A+0NYN91Ll+VZTTQLI8ldOX3uqqgDbrWPapOVuhebqjSTN bw8h3rUzUDpAbDEs2+R/+EccV1XfIY5dhb5ZXQj0BocPeX6MguHtlRSJnv6FxsY3 vcQpyIYaUUHbVoN5GUyDSO/LHgchAlqxOzMOXVu6ufsUGIr9kx0S54IaYw6I =4Y7y -----END PGP PRIVATE KEY BLOCK----- gnome-keysign-0.9/tests/test_gpgmeh.py000066400000000000000000000436171310507150200201650ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import os, sys from subprocess import CalledProcessError, check_call import tempfile from nose.tools import * import gpg from keysign.gpgmeh import TempContext from keysign.gpgmeh import DirectoryContext from keysign.gpgmeh import UIDExport from keysign.gpgmeh import export_uids from keysign.gpgmeh import fingerprint_from_keydata from keysign.gpgmeh import openpgpkey_from_data from keysign.gpgmeh import get_usable_keys from keysign.gpgmeh import get_usable_secret_keys from keysign.gpgmeh import get_public_key_data from keysign.gpgmeh import sign_keydata_and_encrypt log = logging.getLogger(__name__) thisdir = os.path.dirname(os.path.realpath(__file__)) parentdir = os.path.join(thisdir, "..") def get_fixture_dir(fixture=""): dname = os.path.join(thisdir, "fixtures", fixture) return dname def get_fixture_file(fixture): fname = os.path.join(get_fixture_dir(), fixture) return fname def read_fixture_file(fixture): fname = get_fixture_file(fixture) data = open(fname, 'rb').read() return data @raises(ValueError) def test_uid_export_0(): "You should not be able to export uid < 1" data = read_fixture_file("pubkey-1.asc") uid_data = UIDExport(data, 0) assert False def test_uid_export_single(): # This key contains only one UID data = read_fixture_file("pubkey-1.asc") try: uid1_data = UIDExport(data, 1) except KeyboardInterrupt as e: log.exception("Meh.") raise RuntimeError() # The original key c = TempContext() c.op_import(data) result = c.op_import_result() logging.info("Result: %r", result) fpr = result.imports[0].fpr uids = c.get_key(fpr).uids assert_equals(1, len(uids)) # The first exported UID c = TempContext() logging.info("uid1: %r", uid1_data) c.op_import(uid1_data) result = c.op_import_result() imports = result.imports assert_equals(1, len(imports)) uids1_key = c.get_key(fpr).uids assert_equals(1, len(uids1_key)) uid1 = uids1_key[0] # assert_equals(uid1, uids[0]) assert_equals(uid1.uid, uids[0].uid) def test_uid_export_double(): # This key contains two UIDs data = read_fixture_file("pubkey-2-uids.asc") try: uid1_data = UIDExport(data, 1) logging.info("uid1: %r", uid1_data) uid2_data = UIDExport(data, 2) except KeyboardInterrupt as e: log.exception("Meh.") raise RuntimeError() assert_not_equals(uid1_data, uid2_data) # The original key c = TempContext() c.op_import(data) result = c.op_import_result() logging.info("Result: %r", result) fpr = result.imports[0].fpr uids = c.get_key(fpr).uids assert_equals(2, len(uids)) # The first exported UID c = TempContext() logging.info("uid1: %r", uid1_data) c.op_import(uid1_data) result = c.op_import_result() imports = result.imports assert_equals(1, len(imports)) uids1_key = c.get_key(fpr).uids assert_equals(1, len(uids1_key)) uid1 = uids1_key[0] # assert_equals(uid1, uids[0]) assert_equals(uid1.uid, uids[0].uid) # The second exported UID c = TempContext() c.op_import(uid2_data) result = c.op_import_result() imports = result.imports assert_equals(1, len(imports)) uids2_key = c.get_key(fpr).uids assert_equals(1, len(uids2_key)) uid2 = uids2_key[0] # FIXME: The objects don't implement __eq__ it seems :-/ # assert_equals(uid2, uids[1]) assert_equals(uid2.uid, uids[1].uid) def test_export_uids(): # This key contains two UIDs # We ought to have tests with revoked and invalid UIDs data = read_fixture_file("pubkey-2-uids.asc") # The original key c = TempContext() c.op_import(data) result = c.op_import_result() logging.info("Result: %r", result) fpr = result.imports[0].fpr uids = c.get_key(fpr).uids assert_equals(2, len(uids)) exported_uids = list(export_uids(data)) assert_equals(2, len(exported_uids)) exported_uid1 = exported_uids[0] uid1, uid1_data = exported_uid1 exported_uid2 = exported_uids[1] uid2, uid2_data = exported_uid2 assert_equals(uids[0].uid, uid1) assert_equals(uids[1].uid, uid2) # The first exported UID c = TempContext() c.op_import(uid1_data) result = c.op_import_result() imports = result.imports assert_equals(1, len(imports)) uids1_key = c.get_key(fpr).uids assert_equals(1, len(uids1_key)) uid1_key = uids1_key[0] # assert_equals(uid1, uids[0]) assert_equals(uid1_key.uid, uids[0].uid) # The second exported UID c = TempContext() c.op_import(uid2_data) result = c.op_import_result() imports = result.imports assert_equals(1, len(imports)) uids2_key = c.get_key(fpr).uids assert_equals(1, len(uids2_key)) uid2_key = uids2_key[0] # FIXME: The objects don't implement __eq__ it seems :-/ # assert_equals(uid2, uids[1]) assert_equals(uid2_key.uid, uids[1].uid) def test_export_alpha_uids(): """When UIDs get deleted, their index shrinks, of course We didn't, however, take that into account so a key with three UIDs would break. """ data = read_fixture_file("alpha.asc") # The original key c = TempContext() c.op_import(data) result = c.op_import_result() logging.info("Result: %r", result) fpr = result.imports[0].fpr uids = c.get_key(fpr).uids logging.info("UIDs: %r", uids) assert_equals(3, len(uids)) for i, uid in enumerate(uids, start=1): exported_uid = UIDExport(data, i) tmp = TempContext() tmp.op_import(exported_uid) result = tmp.op_import_result() logging.debug("UID %d %r import result: %r", i, uid, result) uid_key = tmp.get_key(result.imports[0].fpr) assert_equals(1, len(uid_key.uids)) key_uid = uid_key.uids[0] # FIXME: Enable __eq__ # assert_equal(uids[i-1], key_uid) assert_equal(uids[i-1].name, key_uid.name) assert_equal(uids[i-1].email, key_uid.email) @raises(ValueError) def test_fingerprint_from_data(): fingerprint = fingerprint_from_keydata("This is not a key...") assert False class TestKey1: def setup(self): data = read_fixture_file("pubkey-1.asc") self.key = openpgpkey_from_data(data) def test_fingerprint(self): assert_equals("ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55", self.key.fingerprint) def test_uids(self): uids = self.key.uidslist assert_equals(1, len(uids)) uid = uids[0] assert_equals('Joe Random Hacker', uid.name) assert_equals('joe@example.com', uid.email) @raises(ValueError) def test_get_public_key_no_data(): tmp = tempfile.mkdtemp() d = get_public_key_data(None, homedir=tmp) assert_equals("", d) class TestGetPublicKeyData: def setup(self): self.fname = get_fixture_file("pubkey-1.asc") original = open(self.fname, 'rb').read() # This should be a new, empty directory self.homedir = tempfile.mkdtemp() gpgcmd = ["gpg", "--homedir={}".format(self.homedir)] # The directory should not have any keys # I don't know how to easily check for that, though # Now we import a single key check_call(gpgcmd + ["--import", self.fname]) self.originalkey = openpgpkey_from_data(original) def teardown(self): # shutil.rmtree(self.homedir) pass def test_get_all_public_key_data(self): # Hm. The behaviour of something that matches # more than one key may change. data = get_public_key_data("", homedir=self.homedir) newkey = openpgpkey_from_data(data) # Hrm. We may be better off checking for a few things # we actually care about rather than delegating to the Key() itself. assert_equals(self.originalkey, newkey) def test_get_public_key_data(self): fpr = self.originalkey.fingerprint data = get_public_key_data(fpr, homedir=self.homedir) newkey = openpgpkey_from_data(data) assert_equals(fpr, newkey.fingerprint) @raises(ValueError) def test_no_match(self): data = get_public_key_data("nothing should match this", homedir=self.homedir) newkey = openpgpkey_from_data(data) assert False def test_get_empty_usable_keys(): homedir = tempfile.mkdtemp() keys = get_usable_keys(homedir=homedir) assert_equals(0, len(keys)) class TestGetUsableKeys: def setup(self): self.fname = get_fixture_file("pubkey-1.asc") original = open(self.fname, 'rb').read() # This should be a new, empty directory self.homedir = tempfile.mkdtemp() gpgcmd = ["gpg", "--homedir={}".format(self.homedir)] # The directory should not have any keys # I don't know how to easily check for that, though # Now we import a single key check_call(gpgcmd + ["--import", self.fname]) self.originalkey = openpgpkey_from_data(original) def teardown(self): # shutil.rmtree(self.homedir) pass def test_get_usable_key_no_pattern(self): keys = get_usable_keys(homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(self.originalkey, key) def test_get_usable_key_fpr(self): fpr = self.originalkey.fingerprint keys = get_usable_keys(fpr, homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(fpr, self.originalkey.fingerprint) class TestGetUsableSecretKeys: def setup(self): self.fname = get_fixture_file("seckey-1.asc") original = open(self.fname, 'rb').read() # This should be a new, empty directory self.homedir = tempfile.mkdtemp() gpgcmd = ["gpg", "--homedir={}".format(self.homedir)] # The directory should not have any keys # I don't know how to easily check for that, though # Now we import a single key check_call(gpgcmd + ["--import", self.fname]) self.originalkey = openpgpkey_from_data(original) def teardown(self): # shutil.rmtree(self.homedir) pass def test_get_usable_key_no_pattern(self): keys = get_usable_secret_keys(homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(self.originalkey, key) def test_get_usable_key_fpr(self): fpr = self.originalkey.fingerprint keys = get_usable_secret_keys(fpr, homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(fpr, self.originalkey.fingerprint) def get_signatures_for_uids_on_key(ctx, key): """It seems to be a bit hard to get a key with its signatures, so this is a small helper function""" # esp. get_key does not take a SIGS argument. # What happens if keylist returns multiple keys, e.g. because there # is another key with a UID named as the fpr? How can I make sure I # get the signatures of any given key? keys = list(ctx.keylist(key.fpr, mode=(gpg.constants.keylist.mode.LOCAL |gpg.constants.keylist.mode.SIGS))) assert len(keys) == 1 uid_sigs = {uid.uid: [s for s in uid.signatures] for uid in keys[0].uids} log.info("Signatures: %r", uid_sigs) return uid_sigs class TestSignAndEncrypt: SENDER_KEY = "seckey-no-pw-1.asc" RECEIVER_KEY = "seckey-no-pw-2.asc" def setup(self): self.key_sender_key = get_fixture_file(self.SENDER_KEY) self.key_receiver_key = get_fixture_file(self.RECEIVER_KEY) # This should be a new, empty directory self.key_sender_homedir = tempfile.mkdtemp() self.key_receiver_homedir = tempfile.mkdtemp() sender_gpgcmd = ["gpg", "--homedir={}".format(self.key_sender_homedir)] receiver_gpgcmd = ["gpg", "--homedir={}".format(self.key_receiver_homedir)] check_call(sender_gpgcmd + ["--import", self.key_sender_key]) check_call(receiver_gpgcmd + ["--import", self.key_receiver_key]) def teardown(self): # shutil.rmtree(self.sender_homedir) # shutil.rmtree(self.receiver_homedir) pass def test_sign_and_encrypt(self): secret_keydata = open(self.key_sender_key, "rb").read() # We get the public portion of the key sender = TempContext() sender.op_import(secret_keydata) result = sender.op_import_result() fpr = result.imports[0].fpr sink = gpg.Data() sender.op_export(fpr, 0, sink) sink.seek(0, 0) # This is the key that we will sign public_sender_key = sink.read() keys = get_usable_secret_keys(homedir=self.key_sender_homedir) assert_equals(1, len(keys)) key = keys[0] uids = key.uidslist # Now finally call the function under test uid_encrypted = list(sign_keydata_and_encrypt(public_sender_key, error_cb=None, homedir=self.key_receiver_homedir)) assert_equals(len(uids), len(uid_encrypted)) # We need to explicitly request signatures uids_before = uids assert_equals (len(uids_before), len(sender.get_key(fpr).uids)) sigs_before = [s for l in get_signatures_for_uids_on_key(sender, key).values() for s in l] for uid, uid_enc in zip(uids_before, uid_encrypted): # The test doesn't work so well, because comments # are not rendered :-/ # assert_equals(uid, uid_enc[0]) assert_in(uid.name, uid_enc[0].uid) assert_in(uid.email, uid_enc[0].uid) ciphertext = uid_enc[1] log.debug("Decrypting %r", ciphertext) plaintext, result, vrfy = sender.decrypt(ciphertext) log.debug("Decrypt Result: %r", result) sender.op_import(plaintext) import_result = sender.op_import_result() log.debug("Import Result: %r", import_result) assert_equals(1, import_result.new_signatures) updated_key = sender.get_key(fpr) log.debug("updated key: %r", updated_key) log.debug("updated key sigs: %r", [(uid, uid.signatures) for uid in updated_key.uids]) sigs_after = [s for l in get_signatures_for_uids_on_key(sender, key).values() for s in l] assert_greater(len(sigs_after), len(sigs_before)) def test_sign_and_encrypt_double_secret(self): "We want to produce as many signatures as possible" recv = DirectoryContext(homedir=self.key_receiver_homedir) params = """ %transient-key Key-Type: RSA Key-Length: 1024 Name-Real: Joe Genkey Tester Name-Comment: with stupid passphrase Name-Email: joe+gpg@example.org %no-protection #Passphrase: Crypt0R0cks #Expire-Date: 2020-12-31 """ recv.op_genkey(params, None, None) gen_result = recv.op_genkey_result() assert_equal(2, len(list(recv.keylist(secret=True)))) sender = DirectoryContext(homedir=self.key_sender_homedir) sender.set_keylist_mode(gpg.constants.KEYLIST_MODE_SIGS) sender_keys = list(sender.keylist()) assert_equal(1, len(sender_keys)) sender_key = sender_keys[0] fpr = sender_key.fpr sink = gpg.Data() sender.op_export_keys(sender_keys, 0, sink) sink.seek(0, 0) public_sender_key = sink.read() # Now finally call the function under test uid_encrypted = list(sign_keydata_and_encrypt(public_sender_key, error_cb=None, homedir=self.key_receiver_homedir)) assert_equals(len(sender_key.uids), len(uid_encrypted)) uids_before = sender.get_key(fpr).uids sigs_before = [s for l in get_signatures_for_uids_on_key(sender, sender_key).values() for s in l] for uid, uid_enc in zip(uids_before, uid_encrypted): # FIXME: assert_equals(uid, uid_enc[0]) assert_in(uid.name, uid_enc[0].uid) assert_in(uid.email, uid_enc[0].uid) ciphertext = uid_enc[1] log.debug("Decrypting %r", ciphertext) plaintext, result, vrfy = sender.decrypt(ciphertext) log.debug("Decrypt Result: %r", result) sender.op_import(plaintext) import_result = sender.op_import_result() log.debug("Import Result: %r", import_result) # Here is the important check for two new signatures assert_equals(2, import_result.new_signatures) updated_key = sender.get_key(fpr) log.debug("updated key: %r", updated_key) log.debug("updated key sigs: %r", [(uid, uid.signatures) for uid in updated_key.uids]) sigs_after = [s for l in get_signatures_for_uids_on_key(sender, sender_key).values() for s in l] assert_greater(len(sigs_after), len(sigs_before)) class TestLatin1(TestSignAndEncrypt): SENDER_KEY = "seckey-latin1.asc" RECEIVER_KEY = "seckey-2.asc" gnome-keysign-0.9/tests/test_gpgmks.py000066400000000000000000000243521310507150200202010ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 Tobias Mueller # # This file is part of GNOME Keysign. # # GNOME Keysign 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. # # GNOME Keysign 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 # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNOME Keysign. If not, see . import logging import os, sys from subprocess import CalledProcessError, check_call import tempfile from nose.tools import * thisdir = os.path.dirname(os.path.realpath(__file__)) parentdir = os.path.join(thisdir, "..") sys.path.insert(0, os.sep.join((parentdir, 'monkeysign'))) from keysign.gpgmks import openpgpkey_from_data from keysign.gpgmks import fingerprint_from_keydata from keysign.gpgmks import get_public_key_data from keysign.gpgmks import get_usable_keys from keysign.gpgmks import get_usable_secret_keys from keysign.gpgmks import sign_keydata_and_encrypt log = logging.getLogger(__name__) def get_fixture_dir(fixture=""): dname = os.path.join(thisdir, "fixtures", fixture) return dname def get_fixture_file(fixture): fname = os.path.join(get_fixture_dir(), fixture) return fname def read_fixture_file(fixture): fname = get_fixture_file(fixture) data = open(fname, 'rb').read() return data @raises(ValueError) def test_openpgpkey_from_no_data(): r = openpgpkey_from_data(None) assert False @raises(ValueError) def test_openpgpkey_from_empty_data(): r = openpgpkey_from_data("") assert False @raises(ValueError) def test_openpgpkey_from_wrong_data(): r = openpgpkey_from_data("this is no key!!1") assert False def test_fingerprint_from_data(): data = read_fixture_file("pubkey-1.asc") fingerprint = fingerprint_from_keydata(data) assert_equals("ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55", fingerprint) @raises(ValueError) def test_fingerprint_from_data(): fingerprint = fingerprint_from_keydata("This is not a key...") assert False class TestKey1: def setup(self): data = read_fixture_file("pubkey-1.asc") self.key = openpgpkey_from_data(data) def test_fingerprint(self): assert_equals("ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55", self.key.fingerprint) def test_uids(self): uids = self.key.uidslist assert_equals(1, len(uids)) uid = uids[0] assert_equals('Joe Random Hacker', uid.name) assert_equals('joe@example.com', uid.email) @raises(ValueError) def test_get_public_key_no_data(): tmp = tempfile.mkdtemp() d = get_public_key_data(None, homedir=tmp) assert_equals("", d) class TestGetPublicKeyData: def setup(self): self.fname = get_fixture_file("pubkey-1.asc") original = open(self.fname, 'rb').read() # This should be a new, empty directory self.homedir = tempfile.mkdtemp() gpgcmd = ["gpg", "--homedir={}".format(self.homedir)] # The directory should not have any keys # I don't know how to easily check for that, though # Now we import a single key check_call(gpgcmd + ["--import", self.fname]) self.originalkey = openpgpkey_from_data(original) def teardown(self): # shutil.rmtree(self.homedir) pass def test_get_all_public_key_data(self): # Hm. The behaviour of something that matches # more than one key may change. data = get_public_key_data("", homedir=self.homedir) newkey = openpgpkey_from_data(data) # Hrm. We may be better off checking for a few things # we actually care about rather than delegating to the Key() itself. assert_equals(self.originalkey, newkey) def test_get_public_key_data(self): fpr = self.originalkey.fingerprint data = get_public_key_data(fpr, homedir=self.homedir) newkey = openpgpkey_from_data(data) assert_equals(fpr, newkey.fingerprint) @raises(ValueError) def test_no_match(self): data = get_public_key_data("nothing should match this", homedir=self.homedir) newkey = openpgpkey_from_data(data) assert False def test_get_empty_usable_keys(): homedir = tempfile.mkdtemp() keys = get_usable_keys(homedir=homedir) assert_equals(0, len(keys)) class TestGetUsableKeys: def setup(self): self.fname = get_fixture_file("pubkey-1.asc") original = open(self.fname, 'rb').read() # This should be a new, empty directory self.homedir = tempfile.mkdtemp() gpgcmd = ["gpg", "--homedir={}".format(self.homedir)] # The directory should not have any keys # I don't know how to easily check for that, though # Now we import a single key check_call(gpgcmd + ["--import", self.fname]) self.originalkey = openpgpkey_from_data(original) def teardown(self): # shutil.rmtree(self.homedir) pass def test_get_usable_key_no_pattern(self): keys = get_usable_keys(homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(self.originalkey, key) def test_get_usable_key_fpr(self): fpr = self.originalkey.fingerprint keys = get_usable_keys(fpr, homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(fpr, self.originalkey.fingerprint) def import_fixture_file_in_random_directory(filename): fname = get_fixture_file(filename) original = open(fname, 'rb').read() # This should be a new, empty directory homedir = tempfile.mkdtemp() gpgcmd = ["gpg", "--homedir={}".format(homedir)] # The directory should not have any keys # I don't know how to easily check for that, though # Now we import a single key check_call(gpgcmd + ["--import", fname]) originalkey = openpgpkey_from_data(original) return homedir, originalkey class TestGetUsableSecretKeys: def setup(self): homedir, key = import_fixture_file_in_random_directory("seckey-1.asc") self.homedir = homedir self.originalkey = key def teardown(self): # shutil.rmtree(self.homedir) pass def test_get_usable_key_no_pattern(self): keys = get_usable_secret_keys(homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(self.originalkey, key) def test_get_usable_key_fpr(self): fpr = self.originalkey.fingerprint keys = get_usable_secret_keys(fpr, homedir=self.homedir) assert_equals(1, len(keys)) key = keys[0] assert_equals(fpr, self.originalkey.fingerprint) class TestSignAndEncrypt: SENDER_KEY = "seckey-no-pw-2.asc" RECEIVER_KEY = "seckey-2.asc" def setup(self): self.sender_key = get_fixture_file(self.SENDER_KEY) self.receiver_key = get_fixture_file(self.RECEIVER_KEY) # This should be a new, empty directory self.sender_homedir = tempfile.mkdtemp() self.receiver_homedir = tempfile.mkdtemp() sender_gpgcmd = ["gpg", "--homedir={}".format(self.sender_homedir)] receiver_gpgcmd = ["gpg", "--homedir={}".format(self.receiver_homedir)] check_call(sender_gpgcmd + ["--import", self.sender_key]) check_call(receiver_gpgcmd + ["--import", self.receiver_key]) def teardown(self): # shutil.rmtree(self.sender_homedir) # shutil.rmtree(self.receiver_homedir) pass def test_sign_and_encrypt(self): keydata = open(self.sender_key, "rb").read() keys = get_usable_secret_keys(homedir=self.sender_homedir) assert_equals(1, len(keys)) key = keys[0] uids = key.uidslist # This is a tuple (uid, encrypted) uid_encrypted = list(sign_keydata_and_encrypt(keydata, error_cb=None, homedir=self.receiver_homedir)) assert_equals(len(uids), len(uid_encrypted)) for plain_uid, enc_uid in zip(uids, uid_encrypted): uid_from_signing = enc_uid[0] signed_uid = enc_uid[1] # The test doesn't work so well, because comments # are not rendered :-/ # assert_in(uid.uid, [e[0] for e in uid_encrypted]) # Decrypt... from monkeysign.gpg import Keyring kr = Keyring(homedir=self.sender_homedir) log.info("encrypted UID: %r", enc_uid) decrypted = kr.decrypt_data(signed_uid) # Now we have the signed UID. We want see if it really carries a signature. from tempfile import mkdtemp current_uid = plain_uid.uid # This is a bit dirty. We should probably rather single out the UID. # Right now we're calling list-sigs on the proper keyring. # The output includes all UIDs and their signatures. # We may get a minimized version from the sign_and_encrypt call. # Or only email addresses but not photo UIDs. # Currently this tests simply checks for the number of signature on a key. # And we expect more after the signing process. # But our test is not reliable because the result of sign_and_encrypt # may be smaller due to, e.g. the photo UIDs mentioned above. kr.context.call_command(b'--list-sigs', current_uid) stdout_before = kr.context.stdout log.debug('Sigs before: %s', stdout_before) after_dir = mkdtemp() kr_after = Keyring(after_dir) kr_after.import_data(decrypted) kr_after.context.call_command('--list-sigs') stdout_after = kr_after.context.stdout log.debug('Sigs after: %s', stdout_after) assert_less(len(stdout_before), len(stdout_after)) class TestLatin1(TestSignAndEncrypt): SENDER_KEY = "seckey-latin1.asc" RECEIVER_KEY = "seckey-2.asc" gnome-keysign-0.9/tox.ini000066400000000000000000000013171310507150200154450ustar00rootroot00000000000000[tox] # I don't seem to be able to say "python 2" and "python 3", only. skip_missing_interpreters = true envlist = py26,py27,py32,py35,flake8 #deps = # -rrequirements.txt [testenv] deps = nose coverage commands=nosetests -v --with-coverage --cover-html --cover-erase --cover-branch --cover-package=keysign --cover-min-percentage=75 #install_command = pip install -U {opts} {packages} [flake8] ignore = E302,E303,W293,E226,E305,E266 max-line-length = 160 exclude = tests/* max-complexity = 10 # Settings specific to the flake8 environment [testenv:flake8] # The command to run: commands = flake8 keysign # We only need flake8 when linting, we do not care about the project dependencies deps = flake8