pax_global_header 0000666 0000000 0000000 00000000064 13105071502 0014504 g ustar 00root root 0000000 0000000 52 comment=ad72a9f305894ea0e65e5f70e4067d722556b83e
gnome-keysign-0.9/ 0000775 0000000 0000000 00000000000 13105071502 0014130 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/.gitignore 0000664 0000000 0000000 00000000200 13105071502 0016110 0 ustar 00root root 0000000 0000000 *.swp
*.pyc
*.bak
/build
/dist
/gnome_keysign.egg-info
.flatpak-builder/
/.coverage
/.noseids
.tox/
cover/
**.flatpak-builder/
gnome-keysign-0.9/.gitmodules 0000664 0000000 0000000 00000000224 13105071502 0016303 0 ustar 00root root 0000000 0000000 [submodule "monkeysign"]
path = monkeysign
#url = https://github.com/muelli/monkeysign.git
url = https://0xacab.org/monkeysphere/monkeysign.git
gnome-keysign-0.9/COPYING 0000664 0000000 0000000 00000104513 13105071502 0015167 0 ustar 00root root 0000000 0000000 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.in 0000664 0000000 0000000 00000000464 13105071502 0015672 0 ustar 00root root 0000000 0000000 # 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/Makefile 0000664 0000000 0000000 00000000026 13105071502 0015566 0 ustar 00root root 0000000 0000000
clean:
rm -f *.pyc
gnome-keysign-0.9/README.rst 0000664 0000000 0000000 00000013273 13105071502 0015625 0 ustar 00root root 0000000 0000000 GNOME 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_NOTES 0000664 0000000 0000000 00000002165 13105071502 0016107 0 ustar 00root root 0000000 0000000 GNOME 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/ 0000775 0000000 0000000 00000000000 13105071502 0015041 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/data/gnome-keysign.appdata.xml 0000664 0000000 0000000 00000001450 13105071502 0021750 0 ustar 00root root 0000000 0000000
gnome-keysign.desktopCC0
<_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.pnghttps://wiki.gnome.org/GnomeKeysigntobiasmue@gnome.org
gnome-keysign-0.9/data/gnome-keysign.desktop 0000664 0000000 0000000 00000000415 13105071502 0021210 0 ustar 00root root 0000000 0000000 [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.svg 0000664 0000000 0000000 00000370350 13105071502 0020346 0 ustar 00root root 0000000 0000000
gnome-keysign-0.9/gnome-keysign.py 0000775 0000000 0000000 00000000535 13105071502 0017264 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 13105071502 0015601 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/keysign/GPGQRCode.py 0000775 0000000 0000000 00000005325 13105071502 0017636 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000006613 13105071502 0020606 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000020057 13105071502 0020250 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000030275 13105071502 0017672 0 ustar 00root root 0000000 0000000 #!/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.py 0000775 0000000 0000000 00000020605 13105071502 0020140 0 ustar 00root root 0000000 0000000 #!/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.py 0000775 0000000 0000000 00000024313 13105071502 0017276 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000013715 13105071502 0020042 0 ustar 00root root 0000000 0000000 #!/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__.py 0000664 0000000 0000000 00000001623 13105071502 0017714 0 ustar 00root root 0000000 0000000 #!/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__.py 0000664 0000000 0000000 00000002067 13105071502 0017700 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000000052 13105071502 0017774 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
__version__ = '0.9'
gnome-keysign-0.9/keysign/app.py 0000664 0000000 0000000 00000025623 13105071502 0016743 0 ustar 00root root 0000000 0000000 #!/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.ui 0000664 0000000 0000000 00000004565 13105071502 0016732 0 ustar 00root root 0000000 0000000
gnome-keysign-0.9/keysign/avahidiscovery.py 0000664 0000000 0000000 00000017311 13105071502 0021176 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000005670 13105071502 0020275 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 13105071502 0017064 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/keysign/compat/__init__.py 0000664 0000000 0000000 00000000000 13105071502 0021163 0 ustar 00root root 0000000 0000000 gnome-keysign-0.9/keysign/compat/gtkbutton.py 0000664 0000000 0000000 00000002065 13105071502 0021462 0 ustar 00root root 0000000 0000000 #!/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.py 0000775 0000000 0000000 00000003170 13105071502 0022457 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000012417 13105071502 0017446 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000034236 13105071502 0017432 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000003541 13105071502 0017260 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000036327 13105071502 0017456 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000027135 13105071502 0021042 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000013573 13105071502 0020332 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000013051 13105071502 0020320 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000013530 13105071502 0021045 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 13105071502 0017272 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/keysign/network/AvahiBrowser.py 0000664 0000000 0000000 00000012500 13105071502 0022236 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000012336 13105071502 0022557 0 ustar 00root root 0000000 0000000 #!/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__.py 0000664 0000000 0000000 00000001531 13105071502 0021403 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000015002 13105071502 0017573 0 ustar 00root root 0000000 0000000 #!/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.ui 0000664 0000000 0000000 00000066111 13105071502 0017567 0 ustar 00root root 0000000 0000000
TrueFalsegtk-applyTrueFalseslide-left-rightscannerTrueFalseverticalFalseTrueFalse6endFalseFalse0False16TrueFalseNo 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>.TrueFalseTrue0FalseFalse0FalseTrue0TrueFalse1001001010vertical6TrueFalse<small>To sign someone's key, scan their QR or enter security code</small>True0FalseTrue1TrueFalse20TrueFalse<b>Camera</b>TrueFalseTrue0TrueFalse0Integrated Web CamTrueTrueend1FalseTrue2TrueFalseverticalTrueTrue3TrueFalsestart<b>Security Code</b>True0FalseTrue4TrueTrueFalseTrue5FalseTrue1scannerScan BarcodeTrueFalse20201010vertical6TrueFalseDownloading key-data. Please wait ...FalseTrue0TrueFalseTrueTrue1FalseKey download was interrupted!FalseTrue2TrueFalsegtk-cancelTrueTrueTrueTrueTrueFalseTrueend0gtk-redoTrueTrueTrueTrueTrueFalseTrueend1FalseTrue3page1page11Confirm Signing KeyTrueFalse20201010vertical6TrueFalseverticalTrueFalseTo 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.True0FalseTrue0TrueFalse1523TrueFalseverticalTrueFalsestartKey0FalseTrue0200TrueFalse23FD 347A 4194 29BA CCD5 E72D 6BC4 7780 54AC D246True0FalseTrue1TrueFalsestart5UIDs0FalseTrue2TrueFalseZulu Test <foo@example.com>
Alpha Bar <example@example.com>True0FalseTrue3FalseTrue0200200TrueFalse5immediatevertical200200TrueFalseendgtk-missing-imageFalseTrue0TrueTrue21TrueTrue1TrueTrue0TrueFalse6C_onfirmconfirm_buttonTrueTrueTrueTrueTrueconfirm-button-imageTrueTrueFalseTrueend0FalseTrueend5page2page22TrueFalse20201010vertical6TrueFalseSigning the following UIDs:FalseTrue0TrueFalseFalseTrue1TrueFalseTrueTrue2FalseFalseTrue3TrueFalsegtk-cancelTrueTrueTrueTrueTrueFalseTrueend0FalseTrueend4page3page33
gnome-keysign-0.9/keysign/scan_barcode.py 0000664 0000000 0000000 00000031475 13105071502 0020570 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000013624 13105071502 0017112 0 ustar 00root root 0000000 0000000 #!/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.ui 0000664 0000000 0000000 00000055674 13105071502 0017112 0 ustar 00root root 0000000 0000000
FalseSelect and send keyTrueFalseslide-left-rightKeylistTrueFalse1040vertical6TrueFalseTrue5errorFalse6endFalseFalse0False16TrueFalseYou don't have any keys!
Please use, e.g. Seahorse to create one.TrueTrue0TrueFalsegtk-dialog-errorFalseTrue1FalseFalse0TrueTrue0TrueFalse1515verticalTrueFalseTrueFalse<b>Select a key for signing</b>True0FalseTrue0TrueFalseTrue0.011764705882352941<small>Times signed</small>TrueFalseTrueend1FalseTrue0400TrueTrueneverinTrueFalse200300TrueFalseTrueTrue1FalseTrue1keylistKeylistTrueFalse51040vertical6TrueFalsestart<small>To have the key signed, the other person must enter the security code, or scan the QR code</small>Truemiddle2FalseTrue60TrueFalseTrueFalsevertical6TrueFalsestart<b>Key Details</b>TrueFalseTrue0TrueFalse6TrueFalsevertical6TrueFalseendFingerprintFalseTrue0TrueFalseendUIDsFalseTrue1FalseTrue0TrueFalsevertical6TrueFalse00FalseTrue0TrueFalse00TrueTrue1TrueTrue1FalseTrue1TrueFalsestart10<b>Security Code</b>TrueFalseTrue2TrueTruestartF289 F7BA 977D F414 3AE9
FDFB F70A 0290 6C30 1813TrueFalseTrue3FalseTrue0TrueFalsevertical400400TrueFalse200noneTrueFalse<b>QR Code</b>True0TrueTrue1TrueTrue1TrueTrue1page1page11headerbarTrueFalseSelect and Send keyKeylistTrueTrueFalseverticalgtk-go-backTrueFalseTrueTrueTrueTrueFalseTrue0
gnome-keysign-0.9/keysign/util.py 0000664 0000000 0000000 00000015374 13105071502 0017142 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 13105071502 0016313 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/packaging/ 0000775 0000000 0000000 00000000000 13105071502 0016054 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/packaging/PKGBUILD 0000664 0000000 0000000 00000002011 13105071502 0017172 0 ustar 00root root 0000000 0000000 # 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.patch 0000664 0000000 0000000 00000001455 13105071502 0022447 0 ustar 00root root 0000000 0000000 diff -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.spec 0000664 0000000 0000000 00000002554 13105071502 0021512 0 ustar 00root root 0000000 0000000 %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.txt 0000664 0000000 0000000 00000000340 13105071502 0017411 0 ustar 00root root 0000000 0000000 # 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.py 0000664 0000000 0000000 00000007022 13105071502 0015643 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 13105071502 0015272 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/tests/fixtures/ 0000775 0000000 0000000 00000000000 13105071502 0017143 5 ustar 00root root 0000000 0000000 gnome-keysign-0.9/tests/fixtures/alpha.asc 0000664 0000000 0000000 00000005055 13105071502 0020725 0 ustar 00root root 0000000 0000000 -----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.asc 0000664 0000000 0000000 00000002721 13105071502 0021272 0 ustar 00root root 0000000 0000000 -----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.asc 0000664 0000000 0000000 00000002400 13105071502 0022227 0 ustar 00root root 0000000 0000000 -----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.asc 0000664 0000000 0000000 00000003243 13105071502 0021256 0 ustar 00root root 0000000 0000000 -----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.asc 0000664 0000000 0000000 00000006567 13105071502 0021273 0 ustar 00root root 0000000 0000000 -----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.asc 0000664 0000000 0000000 00000004066 13105071502 0022312 0 ustar 00root root 0000000 0000000 -----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.asc 0000664 0000000 0000000 00000004210 13105071502 0022307 0 ustar 00root root 0000000 0000000 -----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.asc 0000664 0000000 0000000 00000003573 13105071502 0022323 0 ustar 00root root 0000000 0000000 -----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.py 0000664 0000000 0000000 00000043617 13105071502 0020165 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000024352 13105071502 0020201 0 ustar 00root root 0000000 0000000 #!/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.ini 0000664 0000000 0000000 00000001317 13105071502 0015445 0 ustar 00root root 0000000 0000000 [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