Remove all PySide based code.
16
.gitignore
vendored
@ -1,16 +0,0 @@
|
||||
*.pyc
|
||||
*.egg-info
|
||||
*.egg/
|
||||
.eggs/
|
||||
build/
|
||||
lib/
|
||||
dist/
|
||||
.ropeproject/
|
||||
ChangeLog
|
||||
MANIFEST
|
||||
yubioath/gui/qt_resources.py
|
||||
man/*.1
|
||||
.DS_Store
|
||||
|
||||
# IntelliJ / PyCharm
|
||||
.idea/
|
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "vendor/yubicommon"]
|
||||
path = vendor/yubicommon
|
||||
url = https://github.com/Yubico/python-yubicommon.git
|
19
.travis.yml
@ -1,19 +0,0 @@
|
||||
language: python
|
||||
sudo: false
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
|
||||
install:
|
||||
- pip install --disable-pip-version-check --upgrade pip
|
||||
- pip install flake8
|
||||
|
||||
script:
|
||||
- flake8
|
7
BLURB
@ -1,7 +0,0 @@
|
||||
Author: Yubico
|
||||
Basename: yubioath-desktop
|
||||
Homepage: http://opensource.yubico.com/yubioath-desktop
|
||||
License: GPL-3.0+
|
||||
Name: Yubico Authenticator
|
||||
Project: yubioath-desktop
|
||||
Summary: Crossplatform graphical user interface to generate one-time passwords.
|
674
COPYING
@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
@ -1,8 +0,0 @@
|
||||
include COPYING
|
||||
include NEWS
|
||||
include ChangeLog
|
||||
include screenshot.png
|
||||
include resources/*
|
||||
include qt_resources/*
|
||||
include doc/*.adoc
|
||||
include man/*
|
117
README
@ -1,116 +1 @@
|
||||
== Yubico Authenticator
|
||||
image:https://travis-ci.org/Yubico/yubioath-desktop.svg?branch=master["Build Status", link="https://travis-ci.org/Yubico/yubioath-desktop"]
|
||||
|
||||
The Yubico Authenticator is a graphical desktop tool and command line tool for
|
||||
generating Open AuTHentication (OATH) event-based HOTP and time-based TOTP
|
||||
one-time password codes, with the help of a YubiKey that protects the shared
|
||||
secrets.
|
||||
|
||||
=== Graphical interface
|
||||
image::usage.gif[]
|
||||
|
||||
=== Command line interface
|
||||
$ yubioath --help
|
||||
Usage: yubioath [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Commands:
|
||||
delete Deletes a credential from the YubiKey.
|
||||
gui Launches the Yubico Authenticator graphical interface.
|
||||
password Manage the password used to protect access to the YubiKey.
|
||||
put Stores a new OATH credential in the YubiKey.
|
||||
reset Deletes all stored OATH credentials from the YubiKey.
|
||||
show Print one or more codes from a YubiKey.
|
||||
|
||||
=== Installation
|
||||
The recommended way to install this software including dependencies is by using
|
||||
the provided precompiled binaries for your platform. For Windows and OS X (10.7
|
||||
and above), there are installers available for download
|
||||
https://developers.yubico.com/yubioath-desktop/Releases/[here]. For Ubuntu we
|
||||
have a custom PPA with a package for it
|
||||
https://launchpad.net/~yubico/+archive/ubuntu/stable[here].
|
||||
|
||||
==== Using pip
|
||||
You can also install this project using pip, however the dependencies may
|
||||
require additional tools to build. When installing from pip, you can choose
|
||||
whether or not you wish to install the graphical application, just the command
|
||||
line tool, or just the python library. You do so as follows:
|
||||
|
||||
$ pip install yubioath-desktop[cli,gui] # This will install everything.
|
||||
$ pip install yubioath-desktop # This installs only the library.
|
||||
|
||||
If you run into problems during installation it will likely be with one of the
|
||||
dependencies, such as PySide or pyscard. Please see the relevant documentation
|
||||
for installing each project, provided by those projects.
|
||||
|
||||
You will also need libykpers, which is a C library used to manage credentials
|
||||
using the slot-based approach. Installation instructions for this project is
|
||||
availabe https://developers.yubico.com/yubikey-personalization/[here].
|
||||
|
||||
=== Supported devices
|
||||
Usage of this software requires a compatible YubiKey device. Yubico
|
||||
Authenticator is capable of provisioning and using both slot-based credentials
|
||||
(compatible with any YubiKey that supports OTP) as well as the more powerful
|
||||
standalone OATH functionality of the YubiKey NEO. To use the standalone OATH
|
||||
functionality your YubiKey must have the CCID mode enabled, which can be done
|
||||
by using the https://developers.yubico.com/yubikey-neo-manager/[YubiKey NEO
|
||||
Manager].
|
||||
|
||||
==== Detecting the device when using CCID
|
||||
Under Linux and OS X this application uses libccid to communicate with the
|
||||
YubiKey. This library requires that each card reader used is listed in its
|
||||
configuration file, else the device will not be detected ("Insert a YubiKey..."
|
||||
will be displayed even though a YubiKey is present). To ensure that your
|
||||
libccid configuration contains all necessary entries you can run one of the two
|
||||
files locates in the resources directory of this repository, linux-patch-ccid
|
||||
or osx-patch-ccid, depending on your OS. You will need to run these scripts as
|
||||
root. If installing the OS X version from the binary installer, this script
|
||||
will be run automatically for you.
|
||||
|
||||
NOTE: You may have to reboot your computer for the change to the libccid
|
||||
configuration to take effect!
|
||||
|
||||
=== Dependencies
|
||||
Yubico Authenticator requires click, PySide, yubikey-personalization, pyscard,
|
||||
and PyCrypto.
|
||||
|
||||
=== Working with the source code repository
|
||||
To work with the source code repository, if you wish to build your own release
|
||||
or contribute pull requests, follow these steps to set up your environment. If
|
||||
you just wish to install the application use the pre-build binaries or the
|
||||
source release packages. This project is developed on a Debian based system,
|
||||
other OSes may not be supported for development.
|
||||
|
||||
==== Installing the dependencies
|
||||
Make sure to install the needed dependencies:
|
||||
|
||||
sudo apt-get install python-setuptools python-crypto python-pyscard \
|
||||
python-pyside pyside-tools python-click libykpers-1-1 pcscd
|
||||
|
||||
==== Check out the code
|
||||
Run these commands to check out the source code:
|
||||
|
||||
git clone --recursive https://github.com/Yubico/yubioath-desktop.git
|
||||
|
||||
==== Install the project using pip
|
||||
Install the project from the source code repository, in development mode:
|
||||
|
||||
cd yubioath-desktop # cd into the git repository
|
||||
python setup.py qt_resources # Generate image resources (requires pyside-tools).
|
||||
pip install -e .[cli,gui] # Install the code in developer mode, using pip.
|
||||
|
||||
You can now run the yubioath and yubioath-gui binaries, and anychanges you make
|
||||
to the code should be reflected in these commands. To remove the installation, run:
|
||||
|
||||
pip uninstall yubioath-desktop
|
||||
|
||||
==== Build a source release
|
||||
To build a source release tar ball, run this command:
|
||||
|
||||
python setup.py sdist
|
||||
|
||||
The resulting build will be created in the dist/ subdirectory.
|
||||
|
||||
=== License
|
||||
Yubico Authenticator is licensed under GPLv3+, see COPYING for details.
|
||||
|
||||
Entypo pictograms by Daniel Bruce - www.entypo.com
|
||||
== Yubico Authenticator QML Version
|
||||
|
@ -1,59 +0,0 @@
|
||||
== Building binaries
|
||||
Binaries for Windows and OSX are built using PyInstaller.
|
||||
|
||||
Get the source release file, yubioath-desktop-<version>.tar.gz, and extract it.
|
||||
It should contain a single directory, henceforth refered to as the release
|
||||
directory.
|
||||
|
||||
When building binaries for Windows or OS X, you will need to include
|
||||
.dll/.dylib files from the yubikey-personalization project. Create a
|
||||
subdirectory called "lib" in the release directory. Download the correct binary
|
||||
release for your architecture from
|
||||
https://developers.yubico.com/yubikey-personalization/Releases/[here] and
|
||||
extract the contained .dll/.dylib files to the "lib" directory you created
|
||||
previously.
|
||||
|
||||
=== Windows
|
||||
For Windows you will need python, click, PySide, pyscard, PyCrypto, PyInstaller
|
||||
and Pywin32 installed (32 or 64-bit versions depending on the architecture of
|
||||
the binary your are building).
|
||||
|
||||
To sign the executable you will need signtool.exe (from the Windows SDK) either
|
||||
copied into the root as well or in a location in your PATH, as well as a
|
||||
certificate in the Windows certificate store that you wish to sign with.
|
||||
|
||||
Run "python setup.py executable" from the main release directory.
|
||||
|
||||
With NSIS installed, a Windows installer will be built as well.
|
||||
|
||||
=== OSX
|
||||
For OSX you need python, pyside, pyscard, pycrypto, and pyinstaller installed.
|
||||
One way to install these dependencies is by using Homebrew and pip:
|
||||
|
||||
brew install python
|
||||
brew install pyside
|
||||
pip install PyInstaller
|
||||
pip install pycrypto
|
||||
pip install pyscard
|
||||
pip install click
|
||||
|
||||
NOTE: Homebrew will build backwards-incompatible binaries, so the resulting
|
||||
build will not run on an older version of OSX.
|
||||
|
||||
Run "python setup.py executable" from the main release directory. This
|
||||
will create an .app in the dist directory.
|
||||
|
||||
Sign the code using codesign:
|
||||
|
||||
codesign -s 'Developer ID Application' dist/Yubico\ Authenticator.app --deep
|
||||
|
||||
There is also a project file for use with
|
||||
http://s.sudre.free.fr/Packaging.html[Packages] located at
|
||||
`resources/yubioath.pkgproj`. This can be used to create an installer for
|
||||
distribution, which you should sign prior to distribution:
|
||||
|
||||
packagesbuild resources/osx-installer.pkgproj
|
||||
productsign --sign 'Developer ID Installer' dist/Yubico\ Authenticator.pkg \
|
||||
dist/yubioath-desktop-mac.pkg
|
||||
|
||||
|
@ -1,54 +0,0 @@
|
||||
== Building backwards compatible binaries on OS X
|
||||
|
||||
Building yubioath-desktop compatible with OS X 10.7 (Lion) requires the `CFLAGS` variable to be set.
|
||||
All dependencies also needs to be built with this flag.
|
||||
|
||||
export CFLAGS="-mmacosx-version-min=10.7"
|
||||
|
||||
=== Python
|
||||
|
||||
Install Python 2.7 from python.org
|
||||
|
||||
=== CMake
|
||||
Install CMake from cmake.org
|
||||
|
||||
ln -s /Applications/CMake.app/Contents/bin/cmake /usr/local/bin/cmake
|
||||
|
||||
=== Swig
|
||||
Download swig-3.0.8.tar.gz source from swig.org
|
||||
|
||||
tar -xzcf swig-3.0.8.tar.gz
|
||||
cd swig-3.0.8
|
||||
curl -O ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.38.tar.gz
|
||||
sh Tools/pcre-build.sh
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
|
||||
=== Qt 4
|
||||
A patch is included in this repository to make Qt 4.8.6 compile on OS X.
|
||||
|
||||
curl -O http://download.qt.io/archive/qt/4.8/4.8.6/qt-everywhere-opensource-src-4.8.6.tar.gz
|
||||
tar -xzvf qt-everywhere-opensource-src-4.8.6.tar.gz
|
||||
cd qt-everywhere-opensource-src-4.8.6
|
||||
patch < yubioath-desktop/doc/qt4-8-6-on-osx.patch
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
|
||||
ln -s /usr/local/Trolltech/Qt-4.8.6/bin/qmake /usr/local/bin/qmake
|
||||
|
||||
=== Wheel, pyscard, pycrypto
|
||||
pip install wheel
|
||||
pip install pyscard
|
||||
pip install pycrypto
|
||||
|
||||
=== PySide
|
||||
curl -O wget https://pypi.python.org/packages/source/P/PySide/PySide-1.2.4.tar.gz
|
||||
tar -xzcf PySide-1.2.4.tar.gz
|
||||
cd PySide-1.2.4
|
||||
python2.7 setup.py bdist_wheel
|
||||
pip install dist/PySide-1.2.4-cp27-none-linux-x86_64.whl
|
||||
|
||||
=== Build .app
|
||||
python setup.py executable
|
@ -1,66 +0,0 @@
|
||||
From f20a1135469c7e8c1e4a9be25f9c24dd95ac4dd8 Mon Sep 17 00:00:00 2001
|
||||
From: Dag Heyman <dag@yubico.com>
|
||||
Date: Fri, 3 Jun 2016 12:09:00 +0200
|
||||
Subject: [PATCH] Fixes for building on OS X
|
||||
|
||||
---
|
||||
src/3rdparty/phonon/phonon/objectdescriptionmodel.h | 19 +++++++++++--------
|
||||
src/gui/painting/qpaintengine_mac.cpp | 8 +-------
|
||||
2 files changed, 12 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/src/3rdparty/phonon/phonon/objectdescriptionmodel.h b/src/3rdparty/phonon/phonon/objectdescriptionmodel.h
|
||||
index d994600..3762620 100644
|
||||
--- a/src/3rdparty/phonon/phonon/objectdescriptionmodel.h
|
||||
+++ b/src/3rdparty/phonon/phonon/objectdescriptionmodel.h
|
||||
@@ -139,18 +139,21 @@ namespace Phonon
|
||||
ObjectDescriptionModelDataPrivate *const d;
|
||||
};
|
||||
|
||||
-/* Required to ensure template class vtables are exported on both symbian
|
||||
+/* Required to ensure template class vtables are exported on both symbian
|
||||
and existing builds. */
|
||||
-#if (defined(Q_OS_SYMBIAN) && defined(Q_CC_RVCT)) || defined(Q_CC_CLANG)
|
||||
-// RVCT compiler (2.2.686) requires the export declaration to be on the class to export vtables
|
||||
-// MWC compiler works both ways
|
||||
-// GCCE compiler is unknown (it can't compile QtCore yet)
|
||||
-// Clang also requires the export declaration to be on the class to export vtables
|
||||
+#if defined(Q_OS_SYMBIAN) && defined(Q_CC_RVCT)
|
||||
+// RVCT compiler (2.2.686) requires the export declaration to be on the class to export vtables
|
||||
+// MWC compiler works both ways
|
||||
+// GCCE compiler is unknown (it can't compile QtCore yet)
|
||||
+// Clang also requires the export declaration to be on the class to export vtables
|
||||
#define PHONON_TEMPLATE_CLASS_EXPORT PHONON_EXPORT
|
||||
#define PHONON_TEMPLATE_CLASS_MEMBER_EXPORT
|
||||
+#elif defined(Q_CC_CLANG)
|
||||
+#define PHONON_TEMPLATE_CLASS_EXPORT
|
||||
+#define PHONON_TEMPLATE_CLASS_MEMBER_EXPORT PHONON_EXPORT
|
||||
#else
|
||||
-// Windows builds (at least) do not support export declaration on templated class
|
||||
-// But if you export a member function, the vtable is implicitly exported
|
||||
+// Windows builds (at least) do not support export declaration on templated class
|
||||
+// But if you export a member function, the vtable is implicitly exported
|
||||
#define PHONON_TEMPLATE_CLASS_EXPORT
|
||||
#define PHONON_TEMPLATE_CLASS_MEMBER_EXPORT PHONON_EXPORT
|
||||
#endif
|
||||
diff --git a/src/gui/painting/qpaintengine_mac.cpp b/src/gui/painting/qpaintengine_mac.cpp
|
||||
index 4aa0668..bfe565f 100644
|
||||
--- a/src/gui/painting/qpaintengine_mac.cpp
|
||||
+++ b/src/gui/painting/qpaintengine_mac.cpp
|
||||
@@ -340,13 +340,7 @@ CGColorSpaceRef QCoreGraphicsPaintEngine::macDisplayColorSpace(const QWidget *wi
|
||||
}
|
||||
|
||||
// Get the color space from the display profile.
|
||||
- CGColorSpaceRef colorSpace = 0;
|
||||
- CMProfileRef displayProfile = 0;
|
||||
- CMError err = CMGetProfileByAVID((CMDisplayIDType)displayID, &displayProfile);
|
||||
- if (err == noErr) {
|
||||
- colorSpace = CGColorSpaceCreateWithPlatformColorSpace(displayProfile);
|
||||
- CMCloseProfile(displayProfile);
|
||||
- }
|
||||
+ CGColorSpaceRef colorSpace = CGDisplayCopyColorSpace(displayID);
|
||||
|
||||
// Fallback: use generic DeviceRGB
|
||||
if (colorSpace == 0)
|
||||
--
|
||||
2.7.4 (Apple Git-66)
|
||||
|
@ -1,22 +0,0 @@
|
||||
yubioath\-gui(1)
|
||||
================
|
||||
:doctype: manpage
|
||||
:man source: yubioath-gui
|
||||
:man manual: Yubico Authenticator Manual
|
||||
|
||||
== Name
|
||||
yubioath-gui - Yubico Authenticator graphical interface
|
||||
|
||||
== Synopsis
|
||||
*yubioath-gui*
|
||||
|
||||
== Description
|
||||
The Yubico Authenticator is a graphical desktop tool for generating Open
|
||||
AuTHentication (OATH) event-based HOTP and time-based TOTP one-time password
|
||||
codes, with the help of a YubiKey that protects the shared secrets.
|
||||
|
||||
== Bugs
|
||||
Report bugs in the issue tracker (https://github.com/Yubico/yubioath-desktop/issues)
|
||||
|
||||
== See also
|
||||
*yubioath*(1)
|
@ -1,144 +0,0 @@
|
||||
yubioath(1)
|
||||
===========
|
||||
:doctype: manpage
|
||||
:man source: yubioath
|
||||
:man manual: Yubico Authenticator Manual
|
||||
|
||||
== Name
|
||||
yubioath - Yubico Authenticator command line interface
|
||||
|
||||
== Synopsis
|
||||
*yubioath* [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
== Description
|
||||
The Yubico Authenticator is a graphical desktop tool for generating Open
|
||||
AuTHentication (OATH) event-based HOTP and time-based TOTP one-time password
|
||||
codes, with the help of a YubiKey that protects the shared secrets.
|
||||
|
||||
== Options
|
||||
yubioath has the following options:
|
||||
|
||||
*-v, --version*::
|
||||
Prints the version of the application and exits.
|
||||
|
||||
*-h, --help*::
|
||||
Shows a list of available sub commands and arguments.
|
||||
|
||||
*-R, --remember*::
|
||||
Save any password given for a YubiKey to avoid having to enter it in again.
|
||||
|
||||
*-r, --reader READER*::
|
||||
Name to match smartcard reader against (case insensitive).
|
||||
|
||||
== Commands
|
||||
yubioath supports multiple commands, each with its own options, in addition
|
||||
to the global options:
|
||||
|
||||
=== *show* [OPTIONS] [QUERY]
|
||||
Display one or more one time codes calculated by the YubiKey.
|
||||
|
||||
*-s1, --slot1 DIGITS*::
|
||||
Calculate and show a one time code from slot 1, displaying DIGITS number of
|
||||
digits.
|
||||
|
||||
*-s2, --slot2 DIGITS*::
|
||||
Calculate and show a one time code from slot 2, displaying DIGITS number of
|
||||
digits.
|
||||
|
||||
*-t, --timestamp TIMESTAMP*::
|
||||
Use the user provided TIMESTAMP instead of the system clock.
|
||||
|
||||
*-h, --help*::
|
||||
Shows additional help for the sub command.
|
||||
|
||||
*QUERY*::
|
||||
A filter string to match credential names against. If given, only
|
||||
credentials containing the QUERY substring will be displayed. For HOTP
|
||||
credentials, codes will only be calculated when given a QUERY which
|
||||
uniquely specifices the credential as to avoid unwanted counter
|
||||
incrementation.
|
||||
|
||||
=== *put* [OPTIONS] KEY
|
||||
Load and store a credential into the YubiKey.
|
||||
|
||||
*-S, --destination DEST*::
|
||||
Where DEST is one of:
|
||||
- *0* the main applet (default).
|
||||
- *1* the YubiKey standard slot 1.
|
||||
- *2* the YubiKey standard slot 2.
|
||||
|
||||
*-N, --name NAME*::
|
||||
The name to give the credential. When giving a name with an issuer, the
|
||||
issuer and name should be separated by a colon: _issuer:name_.
|
||||
Not applicable to slot-based credentials.
|
||||
|
||||
*-A, --oath-type ALGORITHM*::
|
||||
OATH algorithm to use. Should be one of *totp* (default) and *hotp*.
|
||||
Not applicable to slot-based credentials.
|
||||
|
||||
*-D, --digits DIGITS*::
|
||||
The number of digits to output when generating codes. Should be *6*
|
||||
(default) or *8*.
|
||||
Not applicable to slot-based credentials.
|
||||
|
||||
*-I IMF, --imf IMF*::
|
||||
The initial value to store for the counter. Only applicable for HOTP
|
||||
credential.
|
||||
Not applicable to slot-based credentials.
|
||||
|
||||
*-T, --touch*::
|
||||
When set, the slot will require the user to press the button on the YubiKey
|
||||
before calculating a code.
|
||||
*Only* applicable to slot-based credentials.
|
||||
|
||||
*-h, --help*::
|
||||
Shows additional help for the sub command.
|
||||
|
||||
*KEY*::
|
||||
Either a base32 encoded key to use as the secret for the credential, or an
|
||||
otpauth:// URI containing the parameters of the credential. When a URI is
|
||||
given the other options are not needed, but can be used to override
|
||||
parameters in the URI, if needed.
|
||||
|
||||
=== *delete* NAME
|
||||
Deletes a credential from the main OATH credential storage.
|
||||
|
||||
*NAME*::
|
||||
A filter string that uniquely identifies the credential to delete.
|
||||
|
||||
=== *password* SUBCOMMAND [OPTIONS]
|
||||
Manage the access password of the OATH applet.
|
||||
|
||||
*set*::
|
||||
Sets a new password for the YubiKey.
|
||||
|
||||
*unset*::
|
||||
Unsets the current password, so that the YubiKey does not require a
|
||||
password to be used.
|
||||
|
||||
*forget*::
|
||||
Remove all access keys stored on disk.
|
||||
|
||||
*-p, --password PASSWORD*::
|
||||
Provide the new password for use with the *set* sub command as an argument.
|
||||
If not given, the command will prompt the user to enter a new password
|
||||
while masking input.
|
||||
|
||||
*-h, --help*::
|
||||
Shows additional help for the sub command.
|
||||
|
||||
=== *reset* [OPTIONS]
|
||||
Factory-reset the OATH applet, unsetting any access password and erasing
|
||||
all stored credentials.
|
||||
|
||||
*-f, --force*::
|
||||
Do not prompt for confirmation before resetting.
|
||||
|
||||
*-h, --help*::
|
||||
Shows additional help for the sub command.
|
||||
|
||||
== Bugs
|
||||
Report bugs in the issue tracker (https://github.com/Yubico/yubioath-desktop/issues)
|
||||
|
||||
== See also
|
||||
*yubioath-gui*(1)
|
Before Width: | Height: | Size: 366 B |
Before Width: | Height: | Size: 256 B |
Before Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 264 B |
@ -1,73 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
DEVICES = [
|
||||
('0x1050', '0x0111', 'Yubico Yubikey NEO OTP+CCID'),
|
||||
('0x1050', '0x0112', 'Yubico Yubikey NEO CCID'),
|
||||
('0x1050', '0x0115', 'Yubico Yubikey NEO U2F+CCID'),
|
||||
('0x1050', '0x0116', 'Yubico Yubikey NEO OTP+U2F+CCID'),
|
||||
('0x1050', '0x0404', 'Yubico Yubikey 4 CCID'),
|
||||
('0x1050', '0x0405', 'Yubico Yubikey 4 OTP+CCID'),
|
||||
('0x1050', '0x0406', 'Yubico Yubikey 4 U2F+CCID'),
|
||||
('0x1050', '0x0407', 'Yubico Yubikey 4 OTP+U2F+CCID')
|
||||
]
|
||||
|
||||
FNAME = "/etc/libccid_Info.plist"
|
||||
|
||||
|
||||
def add_device(dev, content):
|
||||
# Parsing XML with regexes, what a wonderful idea!
|
||||
names = re.search('<key>ifdFriendlyName</key>\s*<array>(.*?)</array>', content, re.DOTALL)
|
||||
|
||||
if names.group(1).find('<string>%s</string>' % dev[2]) > 0:
|
||||
# Already added
|
||||
return content
|
||||
|
||||
print "Adding: %s" % dev[2]
|
||||
|
||||
pos = names.start(1)
|
||||
content = content[:pos] + '\n\t\t<string>%s</string>' % dev[2] + content[pos:]
|
||||
|
||||
vids = re.search('<key>ifdVendorID</key>\s*<array>(.*?)</array>', content, re.DOTALL)
|
||||
pos = vids.start(1)
|
||||
content = content[:pos] + '\n\t\t<string>%s</string>' % dev[0] + content[pos:]
|
||||
|
||||
pids = re.search('<key>ifdProductID</key>\s*<array>(.*?)</array>', content, re.DOTALL)
|
||||
pos = pids.start(1)
|
||||
content = content[:pos] + '\n\t\t<string>%s</string>' % dev[1] + content[pos:]
|
||||
|
||||
return content
|
||||
|
||||
def main():
|
||||
# Patch libccid file:
|
||||
if os.path.isfile(FNAME):
|
||||
print "Updating %s..." % FNAME
|
||||
with open(FNAME, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
for dev in DEVICES:
|
||||
content = add_device(dev, content)
|
||||
|
||||
with open(FNAME, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print "Restarting PCSCD..."
|
||||
try:
|
||||
returncode = subprocess.check_call(['service', 'pcscd', 'restart'])
|
||||
except CalledProcessError:
|
||||
try:
|
||||
subprocess.check_call(['systemctl', 'restart', 'pcscd'])
|
||||
except CalledProcessError:
|
||||
print "Failed to restart pcscd."
|
||||
|
||||
|
||||
else:
|
||||
print "libccid_Info.plist not found, skipping..."
|
||||
|
||||
print "Done!"
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,880 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PACKAGES</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>PACKAGE_FILES</key>
|
||||
<dict>
|
||||
<key>DEFAULT_INSTALL_LOCATION</key>
|
||||
<string>/</string>
|
||||
<key>HIERARCHY</key>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Utilities</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>../dist/Yubico Authenticator.app</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>3</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Applications</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Application Support</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Documentation</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Filesystems</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Frameworks</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Input Methods</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Internet Plug-Ins</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>LaunchAgents</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>LaunchDaemons</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>PreferencePanes</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Preferences</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Printers</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>PrivilegedHelperTools</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>QuickLook</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>QuickTime</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Screen Savers</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Scripts</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Services</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Widgets</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Library</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Extensions</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Library</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>System</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Shared</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>1023</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Users</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>/</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>PAYLOAD_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>VERSION</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>PACKAGE_SCRIPTS</key>
|
||||
<dict>
|
||||
<key>PREINSTALL_PATH</key>
|
||||
<dict/>
|
||||
<key>RESOURCES</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>PACKAGE_SETTINGS</key>
|
||||
<dict>
|
||||
<key>AUTHENTICATION</key>
|
||||
<integer>1</integer>
|
||||
<key>CONCLUSION_ACTION</key>
|
||||
<integer>0</integer>
|
||||
<key>IDENTIFIER</key>
|
||||
<string>com.yubico.pkg.YubicoAuthenticator</string>
|
||||
<key>NAME</key>
|
||||
<string>Yubico Authenticator</string>
|
||||
<key>OVERWRITE_PERMISSIONS</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<string>0.2.0</string>
|
||||
</dict>
|
||||
<key>UUID</key>
|
||||
<string>11112783-0BAA-41FF-86B4-F66A411887E9</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROJECT</key>
|
||||
<dict>
|
||||
<key>PROJECT_COMMENTS</key>
|
||||
<dict>
|
||||
<key>NOTES</key>
|
||||
<data>
|
||||
PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M
|
||||
IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv
|
||||
c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l
|
||||
cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7
|
||||
IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250
|
||||
ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp
|
||||
dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u
|
||||
dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD
|
||||
b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjExMzguNTEiPgo8c3R5bGUg
|
||||
dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5
|
||||
Pgo8L2JvZHk+CjwvaHRtbD4K
|
||||
</data>
|
||||
</dict>
|
||||
<key>PROJECT_PRESENTATION</key>
|
||||
<dict>
|
||||
<key>BACKGROUND</key>
|
||||
<dict>
|
||||
<key>ALIGNMENT</key>
|
||||
<integer>0</integer>
|
||||
<key>BACKGROUND_PATH</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>installer_bg.png</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>CUSTOM</key>
|
||||
<integer>1</integer>
|
||||
<key>SCALING</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>INSTALLATION TYPE</key>
|
||||
<dict>
|
||||
<key>HIERARCHIES</key>
|
||||
<dict>
|
||||
<key>INSTALLER</key>
|
||||
<dict>
|
||||
<key>LIST</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>DESCRIPTION</key>
|
||||
<array/>
|
||||
<key>OPTIONS</key>
|
||||
<dict>
|
||||
<key>HIDDEN</key>
|
||||
<false/>
|
||||
<key>STATE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>PACKAGE_UUID</key>
|
||||
<string>11112783-0BAA-41FF-86B4-F66A411887E9</string>
|
||||
<key>TITLE</key>
|
||||
<array/>
|
||||
<key>TOOLTIP</key>
|
||||
<array/>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>UUID</key>
|
||||
<string>EDABC34C-552C-4359-ABE7-D9A7CA388207</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>REMOVED</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>INSTALLATION TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>INSTALLATION_STEPS</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewIntroductionController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>Introduction</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewReadMeController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>ReadMe</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewLicenseController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>License</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewDestinationSelectController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>TargetSelect</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewInstallationTypeController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>PackageSelection</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewInstallationController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>Install</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewSummaryController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>Summary</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INTRODUCTION</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>LICENSE</key>
|
||||
<dict>
|
||||
<key>KEYWORDS</key>
|
||||
<dict>
|
||||
<key>ORGANIZATION</key>
|
||||
<string>organization</string>
|
||||
<key>OWNER</key>
|
||||
<string>Yubico AB</string>
|
||||
<key>YEAR</key>
|
||||
<string>2014</string>
|
||||
</dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
<key>MODE</key>
|
||||
<integer>0</integer>
|
||||
<key>TEMPLATE</key>
|
||||
<string>BSD License</string>
|
||||
</dict>
|
||||
<key>README</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>SUMMARY</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>TITLE</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>LANGUAGE</key>
|
||||
<string>English</string>
|
||||
<key>VALUE</key>
|
||||
<string>Yubico Authenticator</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>PROJECT_REQUIREMENTS</key>
|
||||
<dict>
|
||||
<key>LIST</key>
|
||||
<array/>
|
||||
<key>POSTINSTALL_PATH</key>
|
||||
<dict/>
|
||||
<key>PREINSTALL_PATH</key>
|
||||
<dict/>
|
||||
<key>RESOURCES</key>
|
||||
<array/>
|
||||
<key>ROOT_VOLUME_ONLY</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>PROJECT_SETTINGS</key>
|
||||
<dict>
|
||||
<key>ADVANCED_OPTIONS</key>
|
||||
<dict/>
|
||||
<key>BUILD_FORMAT</key>
|
||||
<integer>0</integer>
|
||||
<key>BUILD_PATH</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>../dist</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>EXCLUDED_FILES</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.DS_Store</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove .DS_Store files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove ".DS_Store" files created by the Finder.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.pbdevelopment</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove .pbdevelopment files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>CVS</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.cvsignore</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.cvspass</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.svn</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.git</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.gitignore</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove SCM metadata</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>classes.nib</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>designable.db</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>info.nib</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Optimize nib files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>Resources Disabled</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove Resources Disabled folders</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove "Resources Disabled" folders.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SEPARATOR</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NAME</key>
|
||||
<string>Yubico Authenticator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>VERSION</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</plist>
|
@ -1,156 +0,0 @@
|
||||
!include "MUI2.nsh"
|
||||
!include "nsProcess.nsh"
|
||||
|
||||
!define MUI_ICON "yubioath-desktop.ico"
|
||||
|
||||
; The name of the installer
|
||||
Name "Yubico Authenticator"
|
||||
|
||||
; The file to write
|
||||
OutFile "../dist/yubioath-desktop-${VERSION}-win.exe"
|
||||
|
||||
; The default installation directory
|
||||
InstallDir "$PROGRAMFILES\Yubico\Yubico Authenticator"
|
||||
|
||||
; Registry key to check for directory (so if you install again, it will
|
||||
; overwrite the old one automatically)
|
||||
InstallDirRegKey HKLM "Software\Yubico\yubioath-desktop" "Install_Dir"
|
||||
|
||||
SetCompressor /SOLID lzma
|
||||
ShowInstDetails show
|
||||
|
||||
Var MUI_TEMP
|
||||
Var STARTMENU_FOLDER
|
||||
|
||||
;Interface Settings
|
||||
|
||||
!define MUI_ABORTWARNING
|
||||
|
||||
;--------------------------------
|
||||
|
||||
; Pages
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
;Start Menu Folder Page Configuration
|
||||
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "Yubico\Yubico Authenticator"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Yubico\Yubico Authenticator"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
|
||||
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
;Languages
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
|
||||
Section "Kill process" KillProcess
|
||||
${nsProcess::FindProcess} "yubioath.exe" $R0
|
||||
${If} $R0 == 0
|
||||
DetailPrint "Yubico Authenticator (CLI) is running. Closing..."
|
||||
${nsProcess::CloseProcess} "yubioath.exe" $R0
|
||||
Sleep 2000
|
||||
${EndIf}
|
||||
${nsProcess::FindProcess} "yubioath-gui.exe" $R0
|
||||
${If} $R0 == 0
|
||||
DetailPrint "Yubico Authenticator (GUI) is running. Closing..."
|
||||
${nsProcess::CloseProcess} "yubioath-gui.exe" $R0
|
||||
Sleep 2000
|
||||
${EndIf}
|
||||
${nsProcess::Unload}
|
||||
SectionEnd
|
||||
|
||||
|
||||
;--------------------------------
|
||||
|
||||
Section "Yubico Authenticator"
|
||||
SectionIn RO
|
||||
SetOutPath $INSTDIR
|
||||
FILE "..\dist\Yubico Authenticator\*"
|
||||
SectionEnd
|
||||
|
||||
Var MYTMP
|
||||
|
||||
# Last section is a hidden one.
|
||||
Section
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
; Write the installation path into the registry
|
||||
WriteRegStr HKLM "Software\Yubico\yubioath-desktop" "Install_Dir" "$INSTDIR"
|
||||
|
||||
# Windows Add/Remove Programs support
|
||||
StrCpy $MYTMP "Software\Microsoft\Windows\CurrentVersion\Uninstall\yubioath-desktop"
|
||||
WriteRegStr HKLM $MYTMP "DisplayName" "Yubico Authenticator"
|
||||
WriteRegExpandStr HKLM $MYTMP "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||
WriteRegExpandStr HKLM $MYTMP "InstallLocation" "$INSTDIR"
|
||||
WriteRegStr HKLM $MYTMP "DisplayVersion" "${VERSION}"
|
||||
WriteRegStr HKLM $MYTMP "Publisher" "Yubico AB"
|
||||
WriteRegStr HKLM $MYTMP "URLInfoAbout" "http://www.yubico.com"
|
||||
WriteRegDWORD HKLM $MYTMP "NoModify" "1"
|
||||
WriteRegDWORD HKLM $MYTMP "NoRepair" "1"
|
||||
|
||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
||||
|
||||
;Create shortcuts
|
||||
SetShellVarContext all
|
||||
SetOutPath "$SMPROGRAMS\$STARTMENU_FOLDER"
|
||||
CreateShortCut "Yubico Authenticator.lnk" "$INSTDIR\yubioath-gui.exe" "" "$INSTDIR\yubioath-gui.exe" 0
|
||||
CreateShortCut "Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 1
|
||||
!insertmacro MUI_STARTMENU_WRITE_END
|
||||
|
||||
SectionEnd
|
||||
|
||||
; Uninstaller
|
||||
|
||||
Section "Uninstall"
|
||||
|
||||
; Remove registry keys
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\yubioath-desktop"
|
||||
DeleteRegKey HKLM "Software\Yubico\yubioath-desktop"
|
||||
|
||||
; Kill processes
|
||||
${nsProcess::FindProcess} "yubioath.exe" $R0
|
||||
${If} $R0 == 0
|
||||
DetailPrint "Yubico Authenticator (CLI) is running. Closing..."
|
||||
${nsProcess::CloseProcess} "yubioath.exe" $R0
|
||||
Sleep 2000
|
||||
${EndIf}
|
||||
${nsProcess::FindProcess} "yubioath-gui.exe" $R0
|
||||
${If} $R0 == 0
|
||||
DetailPrint "Yubico Authenticator (GUI) is running. Closing..."
|
||||
${nsProcess::CloseProcess} "yubioath-gui.exe" $R0
|
||||
Sleep 2000
|
||||
${EndIf}
|
||||
${nsProcess::Unload}
|
||||
|
||||
; Remove all
|
||||
DELETE "$INSTDIR\*"
|
||||
|
||||
; Remove shortcuts, if any
|
||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
|
||||
SetShellVarContext all
|
||||
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Yubico Authenticator.lnk"
|
||||
|
||||
;Delete empty start menu parent diretories
|
||||
StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
|
||||
|
||||
startMenuDeleteLoop:
|
||||
ClearErrors
|
||||
RMDir $MUI_TEMP
|
||||
GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
|
||||
|
||||
IfErrors startMenuDeleteLoopDone
|
||||
|
||||
StrCmp $MUI_TEMP $SMPROGRAMS startMenuDeleteLoopDone startMenuDeleteLoop
|
||||
startMenuDeleteLoopDone:
|
||||
|
||||
DeleteRegKey /ifempty HKCU "Software\Yubico\yubioath-desktop"
|
||||
|
||||
; Remove directories used
|
||||
RMDir "$INSTDIR"
|
||||
SectionEnd
|
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 21 KiB |
@ -1,11 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=Yubico Authenticator
|
||||
GenericName=Yubico Authenticator
|
||||
Comment=Graphical interface for displaying OATH codes with a Yubikey
|
||||
Exec=yubioath-gui
|
||||
Icon=yubioath
|
||||
StartupNotify=false
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
Keywords=YubiKey;Yubico;Authenticator;
|
@ -1,381 +0,0 @@
|
||||
/* XPM */
|
||||
static char * C:\Users\Dain\Documents\yubico graphics\yubioath_xpm[] = {
|
||||
"32 32 346 2",
|
||||
" c None",
|
||||
". c #DCDDDC",
|
||||
"+ c #E9E9E9",
|
||||
"@ c #EEEEEE",
|
||||
"# c #E9E8E9",
|
||||
"$ c #C3C3C3",
|
||||
"% c #B0B0B0",
|
||||
"& c #DFDFDF",
|
||||
"* c #CFD0CF",
|
||||
"= c #BCBCBC",
|
||||
"- c #F6F6F6",
|
||||
"; c #FEFEFE",
|
||||
"> c #FDFDFD",
|
||||
", c #F7F7F8",
|
||||
"' c #E4E5E5",
|
||||
") c #D7D7D7",
|
||||
"! c #B7B7B7",
|
||||
"~ c #C9C9C9",
|
||||
"{ c #D9D9D9",
|
||||
"] c #D3D4D4",
|
||||
"^ c #CECECE",
|
||||
"/ c #C4C3C4",
|
||||
"( c #9D9C9D",
|
||||
"_ c #E3E2E2",
|
||||
": c #EDEDED",
|
||||
"< c #EFEFEF",
|
||||
"[ c #F5F5F5",
|
||||
"} c #FBFBFB",
|
||||
"| c #FFFFFF",
|
||||
"1 c #E8E8E8",
|
||||
"2 c #9B9B9B",
|
||||
"3 c #8C8D8C",
|
||||
"4 c #E6E6E6",
|
||||
"5 c #DBDBDB",
|
||||
"6 c #D1D1D2",
|
||||
"7 c #CCCCCC",
|
||||
"8 c #C6C6C6",
|
||||
"9 c #C1C2C2",
|
||||
"0 c #B4B4B4",
|
||||
"a c #DDDDDD",
|
||||
"b c #E7E7E7",
|
||||
"c c #F9FAFA",
|
||||
"d c #8F8F8F",
|
||||
"e c #676766",
|
||||
"f c #5E5E5F",
|
||||
"g c #686868",
|
||||
"h c #666767",
|
||||
"i c #666666",
|
||||
"j c #797979",
|
||||
"k c #A3A3A4",
|
||||
"l c #C8C8C8",
|
||||
"m c #C5C4C4",
|
||||
"n c #BFBEBE",
|
||||
"o c #B8B8B8",
|
||||
"p c #B6B6B5",
|
||||
"q c #828282",
|
||||
"r c #E1E1E1",
|
||||
"s c #C2C2C2",
|
||||
"t c #A0A1A0",
|
||||
"u c #E7E6E7",
|
||||
"v c #CBCACB",
|
||||
"w c #616161",
|
||||
"x c #6C6C6C",
|
||||
"y c #AAAAAA",
|
||||
"z c #C0C0C0",
|
||||
"A c #B9B9B9",
|
||||
"B c #AFAFAF",
|
||||
"C c #9F9F9F",
|
||||
"D c #7E7E7E",
|
||||
"E c #666667",
|
||||
"F c #949494",
|
||||
"G c #BBBBBB",
|
||||
"H c #BEBEBE",
|
||||
"I c #818182",
|
||||
"J c #DEDEDE",
|
||||
"K c #CACACA",
|
||||
"L c #989899",
|
||||
"M c #878787",
|
||||
"N c #626262",
|
||||
"O c #C6C7C6",
|
||||
"P c #E0E0E0",
|
||||
"Q c #DCDCDC",
|
||||
"R c #D3D3D3",
|
||||
"S c #CBCBCB",
|
||||
"T c #BABABA",
|
||||
"U c #B2B1B1",
|
||||
"V c #A9AAA9",
|
||||
"W c #A2A2A2",
|
||||
"X c #9A9A9A",
|
||||
"Y c #7A7A7A",
|
||||
"Z c #737373",
|
||||
"` c #C5C6C6",
|
||||
" . c #909090",
|
||||
".. c #A7A7A7",
|
||||
"+. c #DADADA",
|
||||
"@. c #555555",
|
||||
"#. c #8F8E8F",
|
||||
"$. c #EAEAEA",
|
||||
"%. c #EEEEEF",
|
||||
"&. c #D5D5D5",
|
||||
"*. c #CCCBCC",
|
||||
"=. c #9E9E9F",
|
||||
"-. c #979797",
|
||||
";. c #919191",
|
||||
">. c #828283",
|
||||
",. c #6A6A6A",
|
||||
"'. c #898989",
|
||||
"). c #959595",
|
||||
"!. c #A1A1A1",
|
||||
"~. c #C5C5C5",
|
||||
"{. c #828182",
|
||||
"]. c #8E8E8E",
|
||||
"^. c #F9F9F9",
|
||||
"/. c #F7F7F6",
|
||||
"(. c #CDCCCC",
|
||||
"_. c #ACACAC",
|
||||
":. c #9C9C9C",
|
||||
"<. c #838382",
|
||||
"[. c #7D7D7D",
|
||||
"}. c #767676",
|
||||
"|. c #9C9B9C",
|
||||
"1. c #999999",
|
||||
"2. c #D1D1D1",
|
||||
"3. c #BFC0BF",
|
||||
"4. c #ABABAB",
|
||||
"5. c #656464",
|
||||
"6. c #FAFAFA",
|
||||
"7. c #F2F2F2",
|
||||
"8. c #B5B5B5",
|
||||
"9. c #7F7F80",
|
||||
"0. c #727272",
|
||||
"a. c #868686",
|
||||
"b. c #818080",
|
||||
"c. c #959596",
|
||||
"d. c #949393",
|
||||
"e. c #434343",
|
||||
"f. c #BDBEBE",
|
||||
"g. c #5A595A",
|
||||
"h. c #F3F2F3",
|
||||
"i. c #ECECEC",
|
||||
"j. c #E3E3E3",
|
||||
"k. c #9D9D9D",
|
||||
"l. c #B2B2B3",
|
||||
"m. c #B6B7B7",
|
||||
"n. c #AFAEAE",
|
||||
"o. c #535253",
|
||||
"p. c #848584",
|
||||
"q. c #8D8D8D",
|
||||
"r. c #808080",
|
||||
"s. c #9C9B9B",
|
||||
"t. c #8D8C8C",
|
||||
"u. c #6B6B6B",
|
||||
"v. c #ADADAD",
|
||||
"w. c #B1B1B1",
|
||||
"x. c #6E6E6E",
|
||||
"y. c #EBEAEA",
|
||||
"z. c #F1F2F1",
|
||||
"A. c #D1D0D1",
|
||||
"B. c #DADAD9",
|
||||
"C. c #E5E5E5",
|
||||
"D. c #F5F5F4",
|
||||
"E. c #959494",
|
||||
"F. c #C0BFBF",
|
||||
"G. c #AEAEAE",
|
||||
"H. c #A2A2A1",
|
||||
"I. c #8C8C8C",
|
||||
"J. c #5F605F",
|
||||
"K. c #989898",
|
||||
"L. c #868786",
|
||||
"M. c #878786",
|
||||
"N. c #7F7F7F",
|
||||
"O. c #BFC0C0",
|
||||
"P. c #A8A8A8",
|
||||
"Q. c #6F6E6F",
|
||||
"R. c #A9A9A9",
|
||||
"S. c #DBDADA",
|
||||
"T. c #E1E2E2",
|
||||
"U. c #E8E7E7",
|
||||
"V. c #DBDCDB",
|
||||
"W. c #B2B2B2",
|
||||
"X. c #A1A0A1",
|
||||
"Y. c #A6A6A6",
|
||||
"Z. c #5C5C5B",
|
||||
"`. c #8A8989",
|
||||
" + c #818181",
|
||||
".+ c #373838",
|
||||
"++ c #A3A4A4",
|
||||
"@+ c #A8A8A9",
|
||||
"#+ c #5C5D5C",
|
||||
"$+ c #C2C2C1",
|
||||
"%+ c #D2D2D3",
|
||||
"&+ c #D5D4D5",
|
||||
"*+ c #D8D8D8",
|
||||
"=+ c #CECECD",
|
||||
"-+ c #E2E2E2",
|
||||
";+ c #868787",
|
||||
">+ c #5C5C5C",
|
||||
",+ c #A4A5A5",
|
||||
"'+ c #858585",
|
||||
")+ c #888787",
|
||||
"!+ c #494849",
|
||||
"~+ c #5F5E5F",
|
||||
"{+ c #C4C4C4",
|
||||
"]+ c #CACAC9",
|
||||
"^+ c #CDCDCD",
|
||||
"/+ c #D0CFD0",
|
||||
"(+ c #D4D4D4",
|
||||
"_+ c #808081",
|
||||
":+ c #646364",
|
||||
"<+ c #A9AAAA",
|
||||
"[+ c #AFAFB0",
|
||||
"}+ c #B1B0B1",
|
||||
"|+ c #A9A8A9",
|
||||
"1+ c #929292",
|
||||
"2+ c #565656",
|
||||
"3+ c #C7C7C7",
|
||||
"4+ c #646464",
|
||||
"5+ c #C1C1C1",
|
||||
"6+ c #C2C2C3",
|
||||
"7+ c #BFBFBF",
|
||||
"8+ c #9E9E9E",
|
||||
"9+ c #A2A1A2",
|
||||
"0+ c #878887",
|
||||
"a+ c #606060",
|
||||
"b+ c #B9B9B8",
|
||||
"c+ c #B3B3B3",
|
||||
"d+ c #E4E4E4",
|
||||
"e+ c #4B4C4C",
|
||||
"f+ c #B6B6B6",
|
||||
"g+ c #B7B8B8",
|
||||
"h+ c #B7B7B6",
|
||||
"i+ c #B5B4B5",
|
||||
"j+ c #939292",
|
||||
"k+ c #888888",
|
||||
"l+ c #B8B9B8",
|
||||
"m+ c #939493",
|
||||
"n+ c #C1C1C2",
|
||||
"o+ c #4C4B4C",
|
||||
"p+ c #898889",
|
||||
"q+ c #ACADAC",
|
||||
"r+ c #AAAAAB",
|
||||
"s+ c #A3A3A3",
|
||||
"t+ c #636363",
|
||||
"u+ c #CFCFCF",
|
||||
"v+ c #D0D0D0",
|
||||
"w+ c #969695",
|
||||
"x+ c #4C4C4C",
|
||||
"y+ c #848484",
|
||||
"z+ c #838383",
|
||||
"A+ c #A09F9F",
|
||||
"B+ c #9B9A9B",
|
||||
"C+ c #7F7E7F",
|
||||
"D+ c #F0F0F0",
|
||||
"E+ c #F3F3F3",
|
||||
"F+ c #D8D8D9",
|
||||
"G+ c #D0D1D0",
|
||||
"H+ c #B4B5B5",
|
||||
"I+ c #A3A4A3",
|
||||
"J+ c #A5A6A5",
|
||||
"K+ c #323232",
|
||||
"L+ c #9A9A9B",
|
||||
"M+ c #848384",
|
||||
"N+ c #7C7C7C",
|
||||
"O+ c #7A7A7B",
|
||||
"P+ c #9D9E9E",
|
||||
"Q+ c #969696",
|
||||
"R+ c #EEEFEE",
|
||||
"S+ c #70706F",
|
||||
"T+ c #DFDFE0",
|
||||
"U+ c #F6F7F6",
|
||||
"V+ c #A0A0A0",
|
||||
"W+ c #111111",
|
||||
"X+ c #8A8A8A",
|
||||
"Y+ c #8C8D8D",
|
||||
"Z+ c #696969",
|
||||
"`+ c #919291",
|
||||
" @ c #8D8D8C",
|
||||
".@ c #868687",
|
||||
"+@ c #898888",
|
||||
"@@ c #6F6E6E",
|
||||
"#@ c #F1F1F1",
|
||||
"$@ c #F7F7F7",
|
||||
"%@ c #EAEAEB",
|
||||
"&@ c #CBCBCC",
|
||||
"*@ c #8F9090",
|
||||
"=@ c #848585",
|
||||
"-@ c #757575",
|
||||
";@ c #888889",
|
||||
">@ c #939393",
|
||||
",@ c #FEFEFD",
|
||||
"'@ c #F8F8F8",
|
||||
")@ c #DBDBDA",
|
||||
"!@ c #3E3E3E",
|
||||
"~@ c #9D9D9C",
|
||||
"{@ c #747475",
|
||||
"]@ c #7B7B7C",
|
||||
"^@ c #878888",
|
||||
"/@ c #908F8F",
|
||||
"(@ c #E7E8E7",
|
||||
"_@ c #F0F0F1",
|
||||
":@ c #F8F8F9",
|
||||
"<@ c #9F9E9E",
|
||||
"[@ c #A5A5A5",
|
||||
"}@ c #8E8D8E",
|
||||
"|@ c #949594",
|
||||
"1@ c #707070",
|
||||
"2@ c #6F6F6E",
|
||||
"3@ c #656565",
|
||||
"4@ c #F4F3F4",
|
||||
"5@ c #C4C4C5",
|
||||
"6@ c #363636",
|
||||
"7@ c #A4A3A4",
|
||||
"8@ c #AFB0AF",
|
||||
"9@ c #9C9D9D",
|
||||
"0@ c #9A9A99",
|
||||
"a@ c #A0A1A1",
|
||||
"b@ c #E3E3E2",
|
||||
"c@ c #CCCCCD",
|
||||
"d@ c #E4E5E4",
|
||||
"e@ c #E6E5E6",
|
||||
"f@ c #E2E2E1",
|
||||
"g@ c #A6A7A7",
|
||||
"h@ c #030303",
|
||||
"i@ c #A4A4A4",
|
||||
"j@ c #E4E4E3",
|
||||
"k@ c #CACBCB",
|
||||
"l@ c #5F5E5E",
|
||||
"m@ c #0C0C0C",
|
||||
"n@ c #D9D9DA",
|
||||
"o@ c #E6E7E7",
|
||||
"p@ c #070707",
|
||||
"q@ c #E0E0DF",
|
||||
"r@ c #F4F4F4",
|
||||
"s@ c #F6F6F5",
|
||||
"t@ c #BABBBB",
|
||||
"u@ c #3B3B3A",
|
||||
"v@ c #404040",
|
||||
"w@ c #D5D6D5",
|
||||
"x@ c #EBEBEB",
|
||||
"y@ c #484848",
|
||||
"z@ c #343434",
|
||||
"A@ c #585758",
|
||||
"B@ c #747474",
|
||||
"C@ c #626362",
|
||||
"D@ c #383838",
|
||||
"E@ c #0B0B0B",
|
||||
" ",
|
||||
" . + @ # $ % & * = ",
|
||||
" + - ; > , ' ) ! ~ { ] ^ / ( ",
|
||||
" _ : < [ } | | 1 2 3 4 5 6 7 8 9 0 ",
|
||||
" a & b : c ) d e f g h i j k l m n o p q ",
|
||||
" r s t u v w x y $ 8 z A B C D E F G ^ H C I ",
|
||||
" J K ~ L M N O P Q R S s T U V W X Y Z s ` ...h ",
|
||||
" +.l 7 ..@.#.$.%.4 J &.*.s o B ..=.-.;.>.,.'.).!.X ",
|
||||
" ~ ~.S {.].^./.r ) ^ K (.s ! _.:.].M <.q [.}.!.|.1.[. ",
|
||||
" 2.G 3.4.5.$.6.$.b + 7.A s s 8.!.o = 0 9.0.a.b.].c.d.;.e. ",
|
||||
" f.! H g.` @ h.7.R r i.j.k.s l.2 z m.n.o.p.q.;.r.s.].t.u. ",
|
||||
" ~ v.w.C x.P 4 y.z.A.B.C.D.E.F.G.H.G B I.J.d F -.K.).L.M.N. ",
|
||||
" O.P._.Q.R.S.J T.U.V.~ 5 : W._.X.G.% Y.Z.`.K.:.C W X '. +q .+ ",
|
||||
" m.++@+#+$+%+&+*+Q r T =+J -+;+).w.W .>+k.W ,+..R.:.X '+)+!+ ",
|
||||
" n.y C ~+{+]+S ^+/+(+= o ~ i._+;.k.d :+].<+_.G.[+}+|+!.!.1+2+ ",
|
||||
" +.3+Y.4+H z 5+s 6+{+7+8+..T 9+'.0+a.a+l.! ! o b+A c+H d+7+e+ ",
|
||||
" T % r.i f+g+h+f+i+c+w.j+k+-.& l+R.Y m+~.{+$ s n+5+T 0 o I.o+ ",
|
||||
" C [.p+N _.G.q+r+..s+k.'+1.s 7.r ~ t+u+&+v+^ 7 K ~ H 8.w+K.x+ ",
|
||||
" k.y+z+i 2 Y.s+A+B+).q.q C+R D+E+G.).+ -+. F+&.R G+{+H+I+J+K+ ",
|
||||
" L+r.M+N+O+P+X Q+ .'.q I.[.v+R+| S+T+U+: b -+J +.*+u+4.R.V+W+ ",
|
||||
" k+X+Y+Z+F `+ @.@q X+Q++@P @ d+@@#@> $@D+%@4 -+*+&@% B [. ",
|
||||
" :.*@1+=@-@X+'+ +;@1+>@4.& D+I.G.: $@,@'@7.: + )@H ! l+!@ ",
|
||||
" ;.F K.~@{@]@ +^@/@K.r.5 4 7.@.R (@_@^.; :@E+d+R 7+= K. ",
|
||||
" <@k.!.[@x.z+}@|@k.1@x.2@3@q.{ j.i.4@} ; #@r l {+5@6@ ",
|
||||
" 7@8@T 9@N.k+0@a@W Y.B o S ) P 1 < - D+b@o B ^ 1@ ",
|
||||
" 9.c@R G.c+k.M !._.0 = {+^+&.a C.d@-+e@f@^+g@1.h@ ",
|
||||
" Y >@a.7+$ 8 y C i@W.H m ~ 7 *+b $.j@k@>@l@m@ ",
|
||||
" x W.5+~.S 2.n@+.&.s ~ : } '@#@i.4 o@ +p@ ",
|
||||
" ].~.u+R { q@P G+_.i.; c r@s@t@u@ ",
|
||||
" v@=@A +.u w@! +@$@| x@Y.y@ ",
|
||||
" z@A@B@2+g C@D@E@ ",
|
||||
" "};
|
69
setup.py
@ -1,69 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
|
||||
from yubioath.yubicommon.setup import setup
|
||||
from yubioath.yubicommon.setup.qt import qt_resources
|
||||
from yubioath.yubicommon.setup.exe import executable
|
||||
|
||||
setup(
|
||||
name='yubioath-desktop',
|
||||
long_name='Yubico Authenticator',
|
||||
author='Dain Nilsson',
|
||||
author_email='dain@yubico.com',
|
||||
maintainer='Yubico Open Source Maintainers',
|
||||
maintainer_email='ossmaint@yubico.com',
|
||||
url='https://github.com/Yubico/yubioath-desktop',
|
||||
license='GPLv3+',
|
||||
description='Graphical interface for displaying OATH codes with a Yubikey',
|
||||
entry_points={
|
||||
'console_scripts': ['yubioath=yubioath.cli.__main__:main'],
|
||||
'gui_scripts': ['yubioath-gui=yubioath.gui.__main__:main']
|
||||
},
|
||||
setup_requires=[],
|
||||
yc_requires=['ctypes', 'qt'],
|
||||
yc_requires_exclude=['PySide'],
|
||||
install_requires=['pyscard', 'pycrypto'],
|
||||
extras_require={
|
||||
'cli': ['click'],
|
||||
'gui': ['PySide']
|
||||
},
|
||||
cmdclass={
|
||||
'executable': executable,
|
||||
'qt_resources': qt_resources('yubioath.gui')
|
||||
},
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: '
|
||||
'GNU General Public License v3 or later (GPLv3+)',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: X11 Applications :: Qt',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Topic :: Security :: Cryptography',
|
||||
'Topic :: Utilities'
|
||||
]
|
||||
)
|
1
vendor/yubicommon
vendored
@ -1 +0,0 @@
|
||||
Subproject commit fa20421913e356c106b85c4a053d10ecc4a17686
|
@ -1,27 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
__version__ = "3.1.1-dev"
|
@ -1,25 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
@ -1,334 +0,0 @@
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from .. import __version__
|
||||
from ..core.ccid import open_scard
|
||||
from ..core.standard import ALG_SHA1, ALG_SHA256, TYPE_HOTP, TYPE_TOTP
|
||||
from ..core.utils import parse_uri
|
||||
from ..core.exc import NoSpaceError
|
||||
from .keystore import get_keystore
|
||||
from .controller import CliController
|
||||
from time import time
|
||||
from base64 import b32decode
|
||||
import click
|
||||
import sys
|
||||
|
||||
|
||||
def print_creds(results):
|
||||
if not results:
|
||||
click.echo('No credentials found.')
|
||||
return
|
||||
|
||||
longest = max(len(r[0].name) for r in results)
|
||||
format_str = '{:<%d} {:>10}' % longest
|
||||
for (cred, code) in results:
|
||||
if code is None:
|
||||
if cred.oath_type == TYPE_HOTP:
|
||||
code = '[HOTP credential]'
|
||||
elif cred.touch:
|
||||
code = '[Touch credential]'
|
||||
click.echo(format_str.format(cred.name, code))
|
||||
|
||||
|
||||
CLICK_CONTEXT_SETTINGS = dict(
|
||||
help_option_names=['-h', '--help']
|
||||
)
|
||||
|
||||
|
||||
def print_version(ctx, param, value):
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
click.echo('yubioath {}'.format(__version__))
|
||||
ctx.exit()
|
||||
|
||||
|
||||
@click.group(context_settings=CLICK_CONTEXT_SETTINGS)
|
||||
@click.option('-v', '--version', is_flag=True, callback=print_version,
|
||||
expose_value=False, is_eager=True, help='Prints the version of '
|
||||
'the application and exits.')
|
||||
@click.option('-r', '--reader', default='YubiKey', help='Name to match '
|
||||
'smartcard reader against (case insensitive).')
|
||||
@click.option('-R', '--remember', is_flag=True, help='Remember any entered '
|
||||
'access key for later use.')
|
||||
@click.pass_context
|
||||
def cli(ctx, reader, remember):
|
||||
"""
|
||||
Read OATH one time passwords from a YubiKey.
|
||||
"""
|
||||
ctx.obj['dev'] = open_scard(reader)
|
||||
ctx.obj['controller'] = CliController(get_keystore(), remember)
|
||||
ctx.obj['remember'] = remember
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('query', nargs=-1, required=False)
|
||||
@click.option('-s1', '--slot1', type=int, default=0)
|
||||
@click.option('-s2', '--slot2', type=int, default=0)
|
||||
@click.option('-t', '--timestamp', type=int, default=int(time()) + 5)
|
||||
@click.pass_context
|
||||
def show(ctx, query, slot1, slot2, timestamp):
|
||||
"""
|
||||
Print one or more codes from a YubiKey.
|
||||
"""
|
||||
dev = ctx.obj['dev']
|
||||
controller = ctx.obj['controller']
|
||||
|
||||
creds = controller.read_creds(dev, slot1, slot2, timestamp)
|
||||
|
||||
if creds is None:
|
||||
ctx.fail('No YubiKey found!')
|
||||
|
||||
if query:
|
||||
query = ' '.join(query)
|
||||
# Filter based on query. If exact match, show only that result.
|
||||
matched = []
|
||||
for cred, code in creds:
|
||||
if cred.name == query:
|
||||
matched = [(cred, code)]
|
||||
break
|
||||
if query.lower() in cred.name.lower():
|
||||
matched.append((cred, code))
|
||||
|
||||
# Only calculate Touch/HOTP codes if the credential is singled out.
|
||||
if len(matched) == 1:
|
||||
(cred, code) = matched[0]
|
||||
if not code:
|
||||
if cred.touch:
|
||||
controller._prompt_touch()
|
||||
creds = [(cred, cred.calculate(timestamp))]
|
||||
else:
|
||||
creds = [(cred, code)]
|
||||
else:
|
||||
creds = matched
|
||||
|
||||
print_creds(creds)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('key')
|
||||
@click.option('-S', '--destination', type=click.IntRange(0, 2), default=0)
|
||||
@click.option('-N', '--name', required=False, help='Credential name.')
|
||||
@click.option(
|
||||
'-A', '--oath-type', type=click.Choice(['totp', 'hotp']), default='totp',
|
||||
help='Specify whether this is a time or counter-based OATH credential.')
|
||||
@click.option('-D', '--digits', type=click.Choice(['6', '8']), default='6',
|
||||
callback=lambda c, p, v: int(v), help='Number of digits.')
|
||||
@click.option(
|
||||
'-H', '--hmac-algorithm', type=click.Choice(['SHA1', 'SHA256']),
|
||||
default='SHA1', help='HMAC algorithm for OTP generation.')
|
||||
@click.option(
|
||||
'-I', '--imf', type=int, default=0, help='Initial moving factor.')
|
||||
@click.option('-T', '--touch', is_flag=True, help='Require touch.')
|
||||
@click.pass_context
|
||||
def put(
|
||||
ctx, key, destination, name, oath_type,
|
||||
hmac_algorithm, digits, imf, touch):
|
||||
"""
|
||||
Stores a new OATH credential in the YubiKey.
|
||||
"""
|
||||
if key.startswith('otpauth://'):
|
||||
parsed = parse_uri(key)
|
||||
key = parsed['secret']
|
||||
name = parsed.get('name')
|
||||
oath_type = parsed.get('type')
|
||||
hmac_algorithm = parsed.get('algorithm', 'SHA1').upper()
|
||||
digits = int(parsed.get('digits', '6'))
|
||||
imf = int(parsed.get('counter', '0'))
|
||||
|
||||
if oath_type not in ['totp', 'hotp']:
|
||||
ctx.fail('Invalid OATH credential type')
|
||||
|
||||
if hmac_algorithm == 'SHA1':
|
||||
algo = ALG_SHA1
|
||||
elif hmac_algorithm == 'SHA256':
|
||||
algo = ALG_SHA256
|
||||
else:
|
||||
ctx.fail('Invalid HMAC algorithm')
|
||||
|
||||
if digits == 5 and name.startswith('Steam:'):
|
||||
# Steam is a special case where we allow the otpauth
|
||||
# URI to contain a 'digits' value of '5'.
|
||||
digits = 6
|
||||
|
||||
if digits not in [6, 8]:
|
||||
ctx.fail('Invalid number of digits for OTP')
|
||||
|
||||
digits = digits or 6
|
||||
unpadded = key.upper()
|
||||
key = b32decode(unpadded + '=' * (-len(unpadded) % 8))
|
||||
|
||||
controller = ctx.obj['controller']
|
||||
if destination == 0:
|
||||
dev = ctx.obj['dev'] or ctx.fail('No YubiKey found!')
|
||||
name = name or click.prompt('Enter a name for the credential')
|
||||
oath_type = TYPE_TOTP if oath_type == 'totp' else TYPE_HOTP
|
||||
try:
|
||||
controller.add_cred(dev, name, key, oath_type, digits=digits,
|
||||
imf=imf, algo=algo, require_touch=touch)
|
||||
except NoSpaceError:
|
||||
ctx.fail(
|
||||
'There is not enough space to add another credential on your'
|
||||
' device. To create free space to add a new '
|
||||
'credential, delete those you no longer need.')
|
||||
else:
|
||||
controller.add_cred_legacy(destination, key, touch)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('name')
|
||||
@click.pass_context
|
||||
def delete(ctx, name):
|
||||
"""
|
||||
Deletes a credential from the YubiKey.
|
||||
"""
|
||||
controller = ctx.obj['controller']
|
||||
if name in ['YubiKey slot 1', 'YubiKey slot 2']:
|
||||
controller.delete_cred_legacy(int(name[-1]))
|
||||
else:
|
||||
dev = ctx.obj['dev'] or ctx.fail('No YubiKey found!')
|
||||
controller.delete_cred(dev, name)
|
||||
click.echo('Credential deleted!')
|
||||
|
||||
|
||||
@cli.group()
|
||||
def password():
|
||||
"""
|
||||
Manage the password used to protect access to the YubiKey.
|
||||
"""
|
||||
|
||||
|
||||
@password.command()
|
||||
@click.password_option('-p', '--password')
|
||||
@click.pass_context
|
||||
def set(ctx, password):
|
||||
"""
|
||||
Set a new password.
|
||||
"""
|
||||
dev = ctx.obj['dev'] or ctx.fail('No YubiKey found!')
|
||||
controller = ctx.obj['controller']
|
||||
remember = ctx.obj['remember']
|
||||
controller.set_password(dev, password, remember)
|
||||
click.echo('New password set!')
|
||||
|
||||
|
||||
@password.command()
|
||||
@click.pass_context
|
||||
def unset(ctx):
|
||||
"""
|
||||
Removes the need to enter a password to access credentials.
|
||||
"""
|
||||
dev = ctx.obj['dev'] or ctx.fail('No YubiKey found!')
|
||||
controller = ctx.obj['controller']
|
||||
controller.set_password(dev, '')
|
||||
click.echo('Password cleared!')
|
||||
|
||||
|
||||
@password.command()
|
||||
@click.pass_context
|
||||
def forget(ctx):
|
||||
controller = ctx.obj['controller']
|
||||
controller.keystore.clear()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('-f', '--force', is_flag=True,
|
||||
help='Confirm the action without prompting.')
|
||||
@click.pass_context
|
||||
def reset(ctx, force):
|
||||
"""
|
||||
Deletes all stored OATH credentials from non-slot based storage.
|
||||
"""
|
||||
dev = ctx.obj['dev'] or ctx.fail('No YubiKey found!')
|
||||
controller = ctx.obj['controller']
|
||||
force or click.confirm('WARNING!!! Really delete all non slot-based OATH '
|
||||
'credentials from the YubiKey?', abort=True)
|
||||
|
||||
controller.reset_device(dev)
|
||||
click.echo('The OATH functionality of your YubiKey has been reset.\n')
|
||||
|
||||
|
||||
@cli.command(context_settings=dict(
|
||||
ignore_unknown_options=True,
|
||||
allow_extra_args=True
|
||||
))
|
||||
@click.option('-h', '--help', is_flag=True)
|
||||
@click.pass_context
|
||||
def gui(ctx, help):
|
||||
"""
|
||||
Launches the Yubico Authenticator graphical interface.
|
||||
"""
|
||||
try:
|
||||
import PySide
|
||||
assert PySide
|
||||
except ImportError:
|
||||
ctx.fail('GUI requires PySide to run.')
|
||||
import yubioath.gui.__main__
|
||||
sys.argv.remove(ctx.command.name)
|
||||
sys.argv[0] = sys.argv[0] + ' ' + ctx.command.name
|
||||
yubioath.gui.__main__.main()
|
||||
|
||||
|
||||
def intersects(a, b):
|
||||
return bool(set(a) & set(b))
|
||||
|
||||
|
||||
def main():
|
||||
commands = list(cli.commands) + CLICK_CONTEXT_SETTINGS['help_option_names']
|
||||
|
||||
buf = [sys.argv[0]]
|
||||
rest = sys.argv[1:]
|
||||
found_command = False
|
||||
while rest:
|
||||
first = rest.pop(0)
|
||||
if first in commands:
|
||||
found_command = True
|
||||
break # We have a command, no more processing needed.
|
||||
for p in cli.params:
|
||||
if first in p.opts: # first is an option.
|
||||
buf.append(first)
|
||||
if not p.is_flag and rest: # Has a value.
|
||||
buf.append(rest.pop(0))
|
||||
break # Restart checking
|
||||
else: # No match, put the argument back and stop.
|
||||
rest.insert(0, first)
|
||||
break
|
||||
|
||||
if not found_command: # No command found, default to "show".
|
||||
sys.argv = buf + ['show'] + rest
|
||||
|
||||
try:
|
||||
cli(obj={})
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write('\nInterrupted, exiting.\n')
|
||||
sys.exit(130)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,84 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from ..core.controller import Controller
|
||||
from ..core.standard import YubiOathCcid
|
||||
from ..core.exc import CardError
|
||||
from getpass import getpass
|
||||
import sys
|
||||
|
||||
|
||||
class CliController(Controller):
|
||||
|
||||
def __init__(self, keystore, save=False):
|
||||
self.keystore = keystore
|
||||
self._save = save
|
||||
|
||||
def _prompt_touch(self):
|
||||
sys.stderr.write('Touch your YubiKey...\n')
|
||||
|
||||
def unlock(self, device):
|
||||
key = self.keystore.get(device.id)
|
||||
if key:
|
||||
try:
|
||||
device.unlock(key)
|
||||
except CardError:
|
||||
sys.stderr.write('Incorrect password from file.\n')
|
||||
self.keystore.delete(device.id)
|
||||
|
||||
while device.locked:
|
||||
pw = getpass('Password: ')
|
||||
key = device.calculate_key(pw)
|
||||
try:
|
||||
device.unlock(key)
|
||||
if self._save:
|
||||
self.keystore.put(device.id, key)
|
||||
sys.stderr.write('Password saved to %s\n' %
|
||||
self.keystore.fname)
|
||||
except CardError:
|
||||
sys.stderr.write('Incorrect password!\n')
|
||||
|
||||
def set_password(self, ccid_dev, password, remember=False):
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
key = super(CliController, self).set_password(dev, password)
|
||||
if remember:
|
||||
self.keystore.put(dev.id, key)
|
||||
sys.stderr.write('Password saved to %s\n' % self.keystore.fname)
|
||||
else:
|
||||
self.keystore.delete(dev.id)
|
||||
|
||||
def add_cred(self, ccid_dev, *args, **kwargs):
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
super(CliController, self).add_cred(dev, *args, **kwargs)
|
||||
|
||||
def delete_cred(self, ccid_dev, name):
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
super(CliController, self).delete_cred(dev, name)
|
||||
|
||||
def reset_device(self, ccid_dev):
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
self.keystore.delete(dev.id)
|
||||
super(CliController, self).reset_device(dev)
|
@ -1,85 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
import os
|
||||
import json
|
||||
from binascii import b2a_hex, a2b_hex
|
||||
|
||||
CONFIG_HOME = os.path.join(os.path.expanduser('~'), '.yubioath')
|
||||
KEY_FILE = os.path.join(CONFIG_HOME, 'keys.json')
|
||||
|
||||
|
||||
def get_keystore():
|
||||
return Keystore(KEY_FILE)
|
||||
|
||||
|
||||
def _to_hex(val):
|
||||
return b2a_hex(val).decode('ascii')
|
||||
|
||||
|
||||
class Keystore(object):
|
||||
|
||||
def __init__(self, fname):
|
||||
self.fname = fname
|
||||
self._data = {}
|
||||
if os.path.isfile(fname):
|
||||
with open(fname) as f:
|
||||
try:
|
||||
data = json.load(f)
|
||||
if isinstance(data, dict):
|
||||
self._data = data
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def get(self, id):
|
||||
key = _to_hex(id)
|
||||
if key in self._data:
|
||||
return a2b_hex(self._data[key])
|
||||
return None
|
||||
|
||||
def put(self, id, key):
|
||||
if not key:
|
||||
self.delete(id)
|
||||
else:
|
||||
self._data[_to_hex(id)] = _to_hex(key)
|
||||
self._save()
|
||||
|
||||
def delete(self, id):
|
||||
key = _to_hex(id)
|
||||
if key in self._data:
|
||||
del self._data[key]
|
||||
self._save()
|
||||
|
||||
def _save(self):
|
||||
directory = os.path.dirname(self.fname)
|
||||
if not os.path.isdir(directory):
|
||||
os.makedirs(directory)
|
||||
with open(self.fname, 'w') as f:
|
||||
json.dump(self._data, f)
|
||||
|
||||
def clear(self):
|
||||
if os.path.isfile(self.fname):
|
||||
os.remove(self.fname)
|
@ -1,57 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from getpass import getpass
|
||||
from yubioath.core.exc import CardError
|
||||
import sys
|
||||
|
||||
|
||||
class CliUnlocker(object):
|
||||
|
||||
def __init__(self, keystore, save=False):
|
||||
self.keystore = keystore
|
||||
self._save = save
|
||||
|
||||
def __call__(self, device):
|
||||
key = self.keystore.get(device.id)
|
||||
if key:
|
||||
try:
|
||||
device.unlock(key)
|
||||
except CardError:
|
||||
sys.stderr.write('Incorrect password from file.\n')
|
||||
self.keystore.delete(device.id)
|
||||
|
||||
while device.locked:
|
||||
pw = getpass('Password: ')
|
||||
key = device.calculate_key(pw)
|
||||
try:
|
||||
device.unlock(key)
|
||||
if self._save:
|
||||
self.keystore.put(device.id, key)
|
||||
sys.stderr.write('Password saved to %s\n' %
|
||||
self.keystore.fname)
|
||||
except CardError:
|
||||
sys.stderr.write('Incorrect password!\n')
|
@ -1,25 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
@ -1,69 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from smartcard import System
|
||||
from smartcard.Exceptions import SmartcardException
|
||||
|
||||
from yubioath.yubicommon.compat import byte2int, int2byte
|
||||
|
||||
|
||||
class ScardDevice(object):
|
||||
|
||||
"""
|
||||
Pyscard based backend.
|
||||
"""
|
||||
|
||||
def __init__(self, connection):
|
||||
self._conn = connection
|
||||
|
||||
def send_apdu(self, cl, ins, p1, p2, data):
|
||||
header = [cl, ins, p1, p2, len(data)]
|
||||
# from binascii import b2a_hex
|
||||
# print("SEND:", b2a_hex(''.join(map(int2byte, header)) + data))
|
||||
resp, sw1, sw2 = self._conn.transmit(
|
||||
header + [byte2int(b) for b in data])
|
||||
# print("RECV:", b2a_hex(b''.join(map(int2byte, resp + [sw1, sw2]))))
|
||||
return b''.join(int2byte(i) for i in resp), sw1 << 8 | sw2
|
||||
|
||||
def close(self):
|
||||
self._conn.disconnect()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
|
||||
def open_scard(name='Yubikey'):
|
||||
name = name.lower()
|
||||
for reader in System.readers():
|
||||
if name in reader.name.lower():
|
||||
conn = reader.createConnection()
|
||||
try:
|
||||
conn.connect()
|
||||
return ScardDevice(conn)
|
||||
except SmartcardException:
|
||||
pass
|
@ -1,177 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
|
||||
from .standard import YubiOathCcid
|
||||
from .legacy_ccid import LegacyOathCcid
|
||||
from .exc import CardError, InvalidSlotError, NeedsTouchError
|
||||
import time
|
||||
import sys
|
||||
try:
|
||||
from .legacy_otp import open_otp, LegacyOathOtp, LegacyCredential
|
||||
except ImportError:
|
||||
sys.stderr.write('libykpers not found!\n')
|
||||
open_otp = None
|
||||
|
||||
|
||||
class Controller(object):
|
||||
|
||||
@property
|
||||
def otp_supported(self):
|
||||
return bool(open_otp)
|
||||
|
||||
def open_otp(self):
|
||||
otp_dev = open_otp()
|
||||
if otp_dev:
|
||||
return LegacyOathOtp(otp_dev)
|
||||
|
||||
def _prompt_touch(self):
|
||||
pass
|
||||
|
||||
def _end_prompt_touch(self):
|
||||
pass
|
||||
|
||||
def unlock(self, std):
|
||||
raise ValueError('Password required')
|
||||
|
||||
def read_slot_ccid(self, std, slot, digits, timestamp=None):
|
||||
cred = LegacyCredential(std, slot, digits)
|
||||
try:
|
||||
return (cred, cred.calculate(timestamp))
|
||||
except InvalidSlotError:
|
||||
return (cred, 'INVALID')
|
||||
|
||||
def read_slot_otp(self, cred, timestamp=None, use_touch=False):
|
||||
if cred.touch:
|
||||
if not use_touch:
|
||||
raise NeedsTouchError()
|
||||
self._prompt_touch()
|
||||
|
||||
try:
|
||||
return (cred, cred.calculate(timestamp))
|
||||
except InvalidSlotError:
|
||||
return (cred, 'INVALID')
|
||||
except NeedsTouchError:
|
||||
if use_touch:
|
||||
try:
|
||||
time.sleep(0.1) # Give the key a little time...
|
||||
self._prompt_touch()
|
||||
start = time.time()
|
||||
return (cred, cred.calculate(timestamp))
|
||||
except InvalidSlotError:
|
||||
error = 'INVALID' if time.time() - start < 1 else 'TIMEOUT'
|
||||
return (cred, error)
|
||||
return (cred, None)
|
||||
finally:
|
||||
if cred.touch:
|
||||
self._end_prompt_touch()
|
||||
|
||||
def read_creds(self, ccid_dev, slot1, slot2, timestamp, mayblock=True):
|
||||
results = []
|
||||
key_found = False
|
||||
do_legacy = mayblock and bool(slot1 or slot2)
|
||||
legacy_creds = [None, None]
|
||||
|
||||
if ccid_dev:
|
||||
try:
|
||||
std = YubiOathCcid(ccid_dev)
|
||||
key_found = True
|
||||
if std.locked:
|
||||
self.unlock(std)
|
||||
results.extend(std.calculate_all(timestamp))
|
||||
except CardError:
|
||||
pass # No applet?
|
||||
|
||||
if do_legacy:
|
||||
try:
|
||||
legacy = LegacyOathCcid(ccid_dev)
|
||||
for (slot, digits) in [(0, slot1), (1, slot2)]:
|
||||
if digits:
|
||||
try:
|
||||
legacy_creds[slot] = self.read_slot_ccid(
|
||||
legacy, slot+1, digits, timestamp)
|
||||
except NeedsTouchError:
|
||||
pass # Handled over OTP instead
|
||||
except CardError:
|
||||
pass # No applet?
|
||||
|
||||
if self.otp_supported and ((slot1 and not legacy_creds[0])
|
||||
or (slot2 and not legacy_creds[1])):
|
||||
if ccid_dev:
|
||||
ccid_dev.close()
|
||||
legacy = self.open_otp()
|
||||
if legacy:
|
||||
key_found = True
|
||||
if not legacy_creds[0] and slot1:
|
||||
legacy_creds[0] = self.read_slot_otp(
|
||||
LegacyCredential(legacy, 1, slot1), timestamp, True)
|
||||
if not legacy_creds[1] and slot2:
|
||||
legacy_creds[1] = self.read_slot_otp(
|
||||
LegacyCredential(legacy, 2, slot2), timestamp, True)
|
||||
del legacy._device
|
||||
|
||||
if not key_found:
|
||||
return None
|
||||
|
||||
# Add legacy slots first.
|
||||
if legacy_creds[1]:
|
||||
results.insert(0, legacy_creds[1])
|
||||
if legacy_creds[0]:
|
||||
results.insert(0, legacy_creds[0])
|
||||
|
||||
return results
|
||||
|
||||
def set_password(self, dev, password):
|
||||
if dev.locked:
|
||||
self.unlock(dev)
|
||||
key = dev.calculate_key(password)
|
||||
dev.set_key(key)
|
||||
return key
|
||||
|
||||
def add_cred(self, dev, *args, **kwargs):
|
||||
if dev.locked:
|
||||
self.unlock(dev)
|
||||
dev.put(*args, **kwargs)
|
||||
|
||||
def add_cred_legacy(self, *args, **kwargs):
|
||||
legacy = self.open_otp()
|
||||
if not legacy:
|
||||
raise Exception('No YubiKey found!')
|
||||
legacy.put(*args, **kwargs)
|
||||
|
||||
def delete_cred(self, dev, name):
|
||||
if dev.locked:
|
||||
self.unlock(dev)
|
||||
dev.delete(name)
|
||||
|
||||
def delete_cred_legacy(self, slot):
|
||||
legacy = self.open_otp()
|
||||
if not legacy:
|
||||
raise Exception('No YubiKey found!')
|
||||
legacy.delete(slot)
|
||||
|
||||
def reset_device(self, dev):
|
||||
dev.reset()
|
@ -1,59 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
|
||||
class CardError(Exception):
|
||||
|
||||
def __init__(self, status, message=''):
|
||||
super(CardError, self).__init__('Card Error (%04x): %s' %
|
||||
(status, message))
|
||||
self.status = status
|
||||
|
||||
|
||||
class DeviceLockedError(Exception):
|
||||
|
||||
def __init__(self):
|
||||
super(DeviceLockedError, self).__init__('Device is locked!')
|
||||
|
||||
|
||||
class NoSpaceError(Exception):
|
||||
|
||||
def __init__(self):
|
||||
super(NoSpaceError, self).__init__('No space available on device.')
|
||||
|
||||
|
||||
class InvalidSlotError(Exception):
|
||||
|
||||
def __init__(self):
|
||||
super(InvalidSlotError, self).__init__(
|
||||
'The selected slot does not contain a valid OATH credential.')
|
||||
|
||||
|
||||
class NeedsTouchError(Exception):
|
||||
|
||||
def __init__(self):
|
||||
super(NeedsTouchError, self).__init__(
|
||||
'The selected slot needs touch to be used.')
|
@ -1,71 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from .exc import CardError, InvalidSlotError, NeedsTouchError
|
||||
from .utils import (format_code, parse_full, time_challenge)
|
||||
|
||||
YKLEGACY_AID = b'\xa0\x00\x00\x05\x27\x20\x01'
|
||||
|
||||
INS_CHALRESP = 0x01
|
||||
|
||||
SLOTS = [
|
||||
-1,
|
||||
0x30,
|
||||
0x38
|
||||
]
|
||||
|
||||
|
||||
class LegacyOathCcid(object):
|
||||
|
||||
"""
|
||||
CCID interface to a legacy OATH-enabled YubiKey.
|
||||
"""
|
||||
|
||||
def __init__(self, device):
|
||||
self._device = device
|
||||
|
||||
self._select()
|
||||
|
||||
def _send(self, ins, data='', p1=0, p2=0, expected=0x9000):
|
||||
resp, status = self._device.send_apdu(0, ins, p1, p2, data)
|
||||
if expected != status:
|
||||
raise CardError(status)
|
||||
return resp
|
||||
|
||||
def _select(self):
|
||||
self._send(0xa4, YKLEGACY_AID, p1=0x04)
|
||||
|
||||
def calculate(self, slot, digits=6, timestamp=None, mayblock=0):
|
||||
data = time_challenge(timestamp)
|
||||
try:
|
||||
resp = self._send(INS_CHALRESP, data, p1=SLOTS[slot])
|
||||
except CardError as e:
|
||||
if e.status == 0x6985:
|
||||
raise NeedsTouchError()
|
||||
raise
|
||||
if not resp:
|
||||
raise InvalidSlotError()
|
||||
return format_code(parse_full(resp), digits)
|
@ -1,220 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from .utils import time_challenge, parse_full, format_code
|
||||
from .standard import TYPE_TOTP
|
||||
from .exc import InvalidSlotError, NeedsTouchError
|
||||
from yubioath.yubicommon.ctypes import CLibrary
|
||||
from hashlib import sha1
|
||||
from ctypes import (Structure, POINTER, c_int, c_uint8, c_uint, c_char_p,
|
||||
c_bool, sizeof, create_string_buffer, cast, addressof)
|
||||
import weakref
|
||||
|
||||
|
||||
SLOTS = [
|
||||
-1,
|
||||
0x30,
|
||||
0x38
|
||||
]
|
||||
|
||||
YK_KEY = type('YK_KEY', (Structure,), {})
|
||||
|
||||
# Programming
|
||||
SLOT_CONFIG = 1
|
||||
SLOT_CONFIG2 = 3
|
||||
CONFIG1_VALID = 1
|
||||
CONFIG2_VALID = 2
|
||||
|
||||
YKP_CONFIG = type('YKP_CONFIG', (Structure,), {})
|
||||
YK_CONFIG = type('YK_CONFIG', (Structure,), {})
|
||||
YK_STATUS = type('YK_STATUS', (Structure,), {})
|
||||
|
||||
|
||||
class YkPers(CLibrary):
|
||||
_yk_errno_location = [], POINTER(c_int)
|
||||
yk_init = [], bool
|
||||
yk_release = [], bool
|
||||
ykpers_check_version = [c_char_p], c_char_p
|
||||
|
||||
yk_open_first_key = [], POINTER(YK_KEY)
|
||||
yk_close_key = [POINTER(YK_KEY)], bool
|
||||
|
||||
yk_challenge_response = [POINTER(YK_KEY), c_uint8, c_int, c_uint, c_char_p,
|
||||
c_uint, c_char_p], bool
|
||||
|
||||
ykds_alloc = [], POINTER(YK_STATUS)
|
||||
ykds_free = [POINTER(YK_STATUS)], None
|
||||
ykds_touch_level = [POINTER(YK_STATUS)], c_int
|
||||
yk_get_status = [POINTER(YK_KEY), POINTER(YK_STATUS)], c_int
|
||||
|
||||
ykp_alloc = [], POINTER(YKP_CONFIG)
|
||||
ykp_free_config = [POINTER(YKP_CONFIG)], bool
|
||||
|
||||
ykp_configure_version = [POINTER(YKP_CONFIG), POINTER(YK_STATUS)], None
|
||||
|
||||
ykp_HMAC_key_from_raw = [POINTER(YKP_CONFIG), c_char_p], bool
|
||||
ykp_set_tktflag_CHAL_RESP = [POINTER(YKP_CONFIG), c_bool], bool
|
||||
ykp_set_cfgflag_CHAL_HMAC = [POINTER(YKP_CONFIG), c_bool], bool
|
||||
ykp_set_cfgflag_HMAC_LT64 = [POINTER(YKP_CONFIG), c_bool], bool
|
||||
ykp_set_extflag_SERIAL_API_VISIBLE = [POINTER(YKP_CONFIG), c_bool], bool
|
||||
ykp_set_extflag_ALLOW_UPDATE = [POINTER(YKP_CONFIG), c_bool], bool
|
||||
ykp_set_cfgflag_CHAL_BTN_TRIG = [POINTER(YKP_CONFIG), c_bool], bool
|
||||
|
||||
ykp_core_config = [POINTER(YKP_CONFIG)], POINTER(YK_CONFIG)
|
||||
yk_write_command = [POINTER(YK_KEY), POINTER(YK_CONFIG), c_uint8, c_char_p
|
||||
], bool
|
||||
|
||||
def yk_get_errno(self):
|
||||
return self._yk_errno_location().contents.value
|
||||
|
||||
|
||||
ykpers = YkPers('ykpers-1', '1')
|
||||
|
||||
|
||||
YK_ETIMEOUT = 0x04
|
||||
YK_EWOULDBLOCK = 0x0b
|
||||
|
||||
if not ykpers.yk_init():
|
||||
raise Exception("Unable to initialize ykpers")
|
||||
|
||||
ykpers_version = ykpers.ykpers_check_version(None).decode('ascii')
|
||||
|
||||
|
||||
class LegacyOathOtp(object):
|
||||
|
||||
"""
|
||||
OTP interface to a legacy OATH-enabled YubiKey.
|
||||
"""
|
||||
|
||||
def __init__(self, device):
|
||||
self._device = device
|
||||
|
||||
def slot_status(self):
|
||||
st = ykpers.ykds_alloc()
|
||||
ykpers.yk_get_status(self._device, st)
|
||||
tl = ykpers.ykds_touch_level(st)
|
||||
ykpers.ykds_free(st)
|
||||
|
||||
return (
|
||||
bool(tl & CONFIG1_VALID == CONFIG1_VALID),
|
||||
bool(tl & CONFIG2_VALID == CONFIG2_VALID)
|
||||
)
|
||||
|
||||
def calculate(self, slot, digits=6, timestamp=None, mayblock=0):
|
||||
challenge = time_challenge(timestamp)
|
||||
resp = create_string_buffer(64)
|
||||
status = ykpers.yk_challenge_response(
|
||||
self._device, SLOTS[slot], mayblock, len(challenge), challenge,
|
||||
sizeof(resp), resp)
|
||||
if not status:
|
||||
errno = ykpers.yk_get_errno()
|
||||
if errno == YK_EWOULDBLOCK:
|
||||
raise NeedsTouchError()
|
||||
raise InvalidSlotError()
|
||||
return format_code(parse_full(resp.raw[:20]), digits)
|
||||
|
||||
def put(self, slot, key, require_touch=False):
|
||||
if len(key) > 64: # Keys longer than 64 bytes are hashed, as per HMAC.
|
||||
key = sha1(key).digest()
|
||||
if len(key) > 20:
|
||||
raise ValueError('YubiKey slots cannot handle keys over 20 bytes')
|
||||
slot = SLOT_CONFIG if slot == 1 else SLOT_CONFIG2
|
||||
key += b'\x00' * (20 - len(key)) # Keys must be padded to 20 bytes.
|
||||
|
||||
st = ykpers.ykds_alloc()
|
||||
ykpers.yk_get_status(self._device, st)
|
||||
cfg = ykpers.ykp_alloc()
|
||||
ykpers.ykp_configure_version(cfg, st)
|
||||
ykpers.ykds_free(st)
|
||||
ykpers.ykp_set_tktflag_CHAL_RESP(cfg, True)
|
||||
ykpers.ykp_set_cfgflag_CHAL_HMAC(cfg, True)
|
||||
ykpers.ykp_set_cfgflag_HMAC_LT64(cfg, True)
|
||||
ykpers.ykp_set_extflag_SERIAL_API_VISIBLE(cfg, True)
|
||||
ykpers.ykp_set_extflag_ALLOW_UPDATE(cfg, True)
|
||||
if require_touch:
|
||||
ykpers.ykp_set_cfgflag_CHAL_BTN_TRIG(cfg, True)
|
||||
|
||||
if ykpers.ykp_HMAC_key_from_raw(cfg, key):
|
||||
raise ValueError("Error setting the key")
|
||||
|
||||
ycfg = ykpers.ykp_core_config(cfg)
|
||||
try:
|
||||
if not ykpers.yk_write_command(self._device, ycfg, slot, None):
|
||||
raise ValueError("Error writing configuration to key")
|
||||
finally:
|
||||
ykpers.ykp_free_config(cfg)
|
||||
|
||||
def delete(self, slot):
|
||||
slot = SLOT_CONFIG if slot == 1 else SLOT_CONFIG2
|
||||
if not ykpers.yk_write_command(self._device, None, slot, None):
|
||||
raise ValueError("Error writing configuration to key")
|
||||
|
||||
|
||||
class LegacyCredential(object):
|
||||
|
||||
def __init__(self, legacy, slot, digits=6):
|
||||
self.name = 'YubiKey slot %d' % slot
|
||||
self.oath_type = TYPE_TOTP
|
||||
self.touch = None # Touch is unknown
|
||||
self._legacy = legacy
|
||||
self._slot = slot
|
||||
self._digits = digits
|
||||
|
||||
def calculate(self, timestamp=None):
|
||||
try:
|
||||
return self._legacy.calculate(self._slot, self._digits, timestamp,
|
||||
1 if self.touch else 0)
|
||||
except NeedsTouchError:
|
||||
self.touch = True
|
||||
raise
|
||||
else:
|
||||
if self.touch is None:
|
||||
self.touch = False
|
||||
|
||||
def delete(self):
|
||||
self._legacy.delete(self._slot)
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
# Keep track of YK_KEY references.
|
||||
_refs = []
|
||||
|
||||
|
||||
def open_otp():
|
||||
key = ykpers.yk_open_first_key()
|
||||
if key:
|
||||
key_p = cast(addressof(key.contents), POINTER(YK_KEY))
|
||||
|
||||
def cb(ref):
|
||||
_refs.remove(ref)
|
||||
ykpers.yk_close_key(key_p)
|
||||
_refs.append(weakref.ref(key, cb))
|
||||
return key
|
||||
return None
|
@ -1,333 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from __future__ import print_function, division
|
||||
|
||||
from .exc import CardError, DeviceLockedError, NoSpaceError
|
||||
from .utils import (
|
||||
der_read, der_pack, hmac_sha1, derive_key, get_random_bytes,
|
||||
time_challenge, parse_truncated, format_code)
|
||||
from yubioath.yubicommon.compat import int2byte, byte2int
|
||||
import hashlib
|
||||
import struct
|
||||
import os
|
||||
|
||||
YKOATH_AID = b'\xa0\x00\x00\x05\x27\x21\x01\x01'
|
||||
YKOATH_NO_SPACE = 0x6a84
|
||||
|
||||
INS_PUT = 0x01
|
||||
INS_DELETE = 0x02
|
||||
INS_SET_CODE = 0x03
|
||||
INS_RESET = 0x04
|
||||
INS_LIST = 0xa1
|
||||
INS_CALCULATE = 0xa2
|
||||
INS_VALIDATE = 0xa3
|
||||
INS_CALC_ALL = 0xa4
|
||||
INS_SEND_REMAINING = 0xa5
|
||||
|
||||
RESP_MORE_DATA = 0x61
|
||||
|
||||
TAG_NAME = 0x71
|
||||
TAG_NAME_LIST = 0x72
|
||||
TAG_KEY = 0x73
|
||||
TAG_CHALLENGE = 0x74
|
||||
TAG_RESPONSE = 0x75
|
||||
TAG_T_RESPONSE = 0x76
|
||||
TAG_NO_RESPONSE = 0x77
|
||||
TAG_PROPERTY = 0x78
|
||||
TAG_VERSION = 0x79
|
||||
TAG_IMF = 0x7a
|
||||
TAG_ALGO = 0x7b
|
||||
TAG_TOUCH_RESPONSE = 0x7c
|
||||
|
||||
TYPE_MASK = 0xf0
|
||||
TYPE_HOTP = 0x10
|
||||
TYPE_TOTP = 0x20
|
||||
|
||||
ALG_MASK = 0x0f
|
||||
ALG_SHA1 = 0x01
|
||||
ALG_SHA256 = 0x02
|
||||
|
||||
PROP_ALWAYS_INC = 0x01
|
||||
PROP_REQUIRE_TOUCH = 0x02
|
||||
|
||||
SCHEME_STANDARD = 0x00
|
||||
SCHEME_STEAM = 0x01
|
||||
|
||||
STEAM_CHAR_TABLE = "23456789BCDFGHJKMNPQRTVWXY"
|
||||
|
||||
|
||||
def format_code_steam(int_data, digits):
|
||||
chars = []
|
||||
for i in range(5):
|
||||
chars.append(STEAM_CHAR_TABLE[int_data % len(STEAM_CHAR_TABLE)])
|
||||
int_data //= len(STEAM_CHAR_TABLE)
|
||||
return ''.join(chars)
|
||||
|
||||
|
||||
def ensure_unlocked(ykoath):
|
||||
if ykoath.locked:
|
||||
raise DeviceLockedError()
|
||||
|
||||
|
||||
def format_truncated(t_resp, scheme=SCHEME_STANDARD):
|
||||
digits, data = byte2int(t_resp[0]), t_resp[1:]
|
||||
int_data = parse_truncated(data)
|
||||
if scheme == SCHEME_STANDARD:
|
||||
return format_code(int_data, digits)
|
||||
elif scheme == SCHEME_STEAM:
|
||||
return format_code_steam(int_data, digits)
|
||||
|
||||
|
||||
def hmac_shorten_key(key, algo):
|
||||
if algo == ALG_SHA1:
|
||||
h = hashlib.sha1()
|
||||
elif algo == ALG_SHA256:
|
||||
h = hashlib.sha256()
|
||||
else:
|
||||
raise ValueError('Unsupported algorithm!')
|
||||
|
||||
if len(key) > h.block_size:
|
||||
h.update(key)
|
||||
key = h.digest()
|
||||
|
||||
return key
|
||||
|
||||
|
||||
class Credential(object):
|
||||
"""
|
||||
Reference to a credential.
|
||||
"""
|
||||
|
||||
def __init__(self, ykoath, oath_type, name, touch=False):
|
||||
self._ykoath = ykoath
|
||||
self.oath_type = oath_type
|
||||
self.name = name
|
||||
self.touch = touch
|
||||
|
||||
def calculate(self, timestamp=None):
|
||||
return self._ykoath.calculate(self.name, self.oath_type, timestamp)
|
||||
|
||||
def delete(self):
|
||||
self._ykoath.delete(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class _426Device(object):
|
||||
"""
|
||||
The 4.2.0-4.2.6 firmwares have a known issue with credentials that require
|
||||
touch: If this action is performed within 2 seconds of a command resulting
|
||||
in a long response (over 54 bytes), the command will hang. A workaround is
|
||||
to send an invalid command (resulting in a short reply) prior to the
|
||||
"calculate" command.
|
||||
"""
|
||||
|
||||
def __init__(self, delegate):
|
||||
self._delegate = delegate
|
||||
self._long_resp = False
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._delegate, name)
|
||||
|
||||
def send_apdu(self, cl, ins, p1, p2, data):
|
||||
if ins == INS_CALCULATE and self._long_resp:
|
||||
self._delegate.send_apdu(0, 0, 0, 0, '')
|
||||
self._long_resp = False
|
||||
resp, status = self._delegate.send_apdu(cl, ins, p1, p2, data)
|
||||
self._long_resp = len(resp) > 52 # 52 bytes resp, 2 bytes status...
|
||||
return resp, status
|
||||
|
||||
|
||||
class YubiOathCcid(object):
|
||||
|
||||
"""
|
||||
Device interface to a OATH-enabled YubiKey.
|
||||
"""
|
||||
|
||||
def __init__(self, device):
|
||||
self._device = device
|
||||
|
||||
self._select()
|
||||
|
||||
if (4, 2, 0) <= self.version <= (4, 2, 6):
|
||||
self._device = _426Device(device)
|
||||
|
||||
def _send(self, ins, data='', p1=0, p2=0, expected=0x9000):
|
||||
resp, status = self._device.send_apdu(0, ins, p1, p2, data)
|
||||
while (status >> 8) == RESP_MORE_DATA:
|
||||
more, status = self._device.send_apdu(
|
||||
0, INS_SEND_REMAINING, 0, 0, '')
|
||||
resp += more
|
||||
|
||||
if status == YKOATH_NO_SPACE:
|
||||
raise NoSpaceError()
|
||||
|
||||
if expected != status:
|
||||
raise CardError(status)
|
||||
return resp
|
||||
|
||||
def _select(self):
|
||||
resp = self._send(0xa4, YKOATH_AID, p1=0x04)
|
||||
self._version, resp = der_read(resp, TAG_VERSION)
|
||||
self._id, resp = der_read(resp, TAG_NAME)
|
||||
if len(resp) != 0:
|
||||
self._challenge, resp = der_read(resp, TAG_CHALLENGE)
|
||||
else:
|
||||
self._challenge = None
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return tuple(byte2int(d) for d in self._version)
|
||||
|
||||
@property
|
||||
def locked(self):
|
||||
return self._challenge is not None
|
||||
|
||||
def delete(self, name):
|
||||
data = der_pack(TAG_NAME, name.encode('utf8'))
|
||||
self._send(INS_DELETE, data)
|
||||
|
||||
def calculate(self, name, oath_type, timestamp=None):
|
||||
challenge = time_challenge(timestamp) \
|
||||
if oath_type == TYPE_TOTP else b''
|
||||
data = der_pack(
|
||||
TAG_NAME, name.encode('utf8'), TAG_CHALLENGE, challenge)
|
||||
resp = self._send(INS_CALCULATE, data)
|
||||
# Manual dynamic truncation required for Steam entries
|
||||
resp = der_read(resp, TAG_RESPONSE)[0]
|
||||
digits, resp = resp[0:1], resp[1:]
|
||||
offset = byte2int(resp[-1]) & 0xF
|
||||
resp = resp[offset:offset + 4]
|
||||
scheme = SCHEME_STEAM if name.startswith('Steam:') else SCHEME_STANDARD
|
||||
return format_truncated(digits + resp, scheme)
|
||||
|
||||
def calculate_key(self, passphrase):
|
||||
return derive_key(self.id, passphrase)
|
||||
|
||||
def unlock(self, key):
|
||||
if not self.locked:
|
||||
return
|
||||
|
||||
response = hmac_sha1(key, self._challenge)
|
||||
challenge = get_random_bytes(8)
|
||||
verification = hmac_sha1(key, challenge)
|
||||
|
||||
data = der_pack(TAG_RESPONSE, response, TAG_CHALLENGE, challenge)
|
||||
resp = self._send(INS_VALIDATE, data)
|
||||
resp = der_read(resp, TAG_RESPONSE)[0]
|
||||
if resp != verification:
|
||||
raise ValueError('Response did not match verification!')
|
||||
self._challenge = None
|
||||
|
||||
def set_key(self, key=None):
|
||||
ensure_unlocked(self)
|
||||
if key:
|
||||
keydata = int2byte(TYPE_TOTP | ALG_SHA1) + key
|
||||
challenge = get_random_bytes(8)
|
||||
response = hmac_sha1(key, challenge)
|
||||
data = der_pack(TAG_KEY, keydata, TAG_CHALLENGE, challenge,
|
||||
TAG_RESPONSE, response)
|
||||
else:
|
||||
data = der_pack(TAG_KEY, b'')
|
||||
self._send(INS_SET_CODE, data)
|
||||
|
||||
def reset(self):
|
||||
self._send(INS_RESET, p1=0xde, p2=0xad)
|
||||
self._challenge = None
|
||||
|
||||
def list(self):
|
||||
ensure_unlocked(self)
|
||||
resp = self._send(INS_LIST)
|
||||
items = []
|
||||
while resp:
|
||||
data, resp = der_read(resp, TAG_NAME_LIST)
|
||||
items.append(Credential(
|
||||
self,
|
||||
TYPE_MASK & byte2int(data[0]),
|
||||
data[1:],
|
||||
None
|
||||
))
|
||||
return items
|
||||
|
||||
def calculate_all(self, timestamp=None):
|
||||
ensure_unlocked(self)
|
||||
data = der_pack(TAG_CHALLENGE, time_challenge(timestamp))
|
||||
resp = self._send(INS_CALC_ALL, data, p2=1)
|
||||
results = []
|
||||
while resp:
|
||||
name, resp = der_read(resp, TAG_NAME)
|
||||
name = name.decode('utf8')
|
||||
tag, value, resp = der_read(resp)
|
||||
if name.startswith('_hidden:') and 'YKOATH_SHOW_HIDDEN' \
|
||||
not in os.environ:
|
||||
pass # Ignore hidden credentials.
|
||||
elif tag == TAG_T_RESPONSE:
|
||||
# Steam credentials need to be recalculated
|
||||
# to skip full truncation done by Yubikey 4.
|
||||
code = self.calculate(name, TYPE_TOTP) \
|
||||
if name.startswith('Steam:') \
|
||||
else format_truncated(value, SCHEME_STANDARD)
|
||||
results.append((
|
||||
Credential(self, TYPE_TOTP, name, False),
|
||||
code
|
||||
))
|
||||
elif tag == TAG_TOUCH_RESPONSE:
|
||||
results.append((
|
||||
Credential(self, TYPE_TOTP, name, True),
|
||||
None
|
||||
))
|
||||
elif tag == TAG_NO_RESPONSE:
|
||||
results.append((Credential(self, TYPE_HOTP, name), None))
|
||||
else:
|
||||
print("Unsupported tag: %02x" % tag)
|
||||
results.sort(key=lambda a: a[0].name.lower())
|
||||
return results
|
||||
|
||||
def put(self, name, key, oath_type=TYPE_TOTP, algo=ALG_SHA1, digits=6,
|
||||
imf=0, always_increasing=False, require_touch=False):
|
||||
ensure_unlocked(self)
|
||||
key = hmac_shorten_key(key, algo)
|
||||
keydata = int2byte(oath_type | algo) + int2byte(digits) + key
|
||||
data = der_pack(TAG_NAME, name.encode('utf8'), TAG_KEY, keydata)
|
||||
properties = 0
|
||||
if always_increasing:
|
||||
properties |= PROP_ALWAYS_INC
|
||||
if require_touch:
|
||||
if self.version < (4, 2, 6):
|
||||
raise Exception("Touch-required not supported on this key")
|
||||
properties |= PROP_REQUIRE_TOUCH
|
||||
if properties:
|
||||
data += int2byte(TAG_PROPERTY) + int2byte(properties)
|
||||
if imf > 0:
|
||||
data += der_pack(TAG_IMF, struct.pack('>I', imf))
|
||||
self._send(INS_PUT, data)
|
||||
return Credential(self, oath_type, name)
|
@ -1,218 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from yubioath.yubicommon.compat import int2byte, byte2int
|
||||
from Crypto.Hash import HMAC, SHA
|
||||
from Crypto.Protocol.KDF import PBKDF2
|
||||
from Crypto.Random import get_random_bytes
|
||||
try:
|
||||
from urlparse import urlparse, parse_qs
|
||||
from urllib import unquote
|
||||
except ImportError:
|
||||
from urllib.parse import unquote, urlparse, parse_qs
|
||||
import subprocess
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
|
||||
__all__ = [
|
||||
'hmac_sha1',
|
||||
'derive_key',
|
||||
'der_pack',
|
||||
'der_read',
|
||||
'get_random_bytes'
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# OATH related
|
||||
#
|
||||
|
||||
def hmac_sha1(secret, message):
|
||||
return HMAC.new(secret, message, digestmod=SHA).digest()
|
||||
|
||||
|
||||
def time_challenge(t=None):
|
||||
return struct.pack('>q', int((t or time.time())/30))
|
||||
|
||||
|
||||
def parse_full(resp):
|
||||
offs = byte2int(resp[-1]) & 0xf
|
||||
return parse_truncated(resp[offs:offs+4])
|
||||
|
||||
|
||||
def parse_truncated(resp):
|
||||
return struct.unpack('>I', resp)[0] & 0x7fffffff
|
||||
|
||||
|
||||
def format_code(code, digits=6):
|
||||
return ('%%0%dd' % digits) % (code % 10 ** digits)
|
||||
|
||||
|
||||
def parse_uri(uri):
|
||||
uri = uri.strip() # Remove surrounding whitespace
|
||||
parsed = urlparse(uri)
|
||||
if parsed.scheme != 'otpauth': # Not a uri, assume secret.
|
||||
return {'secret': uri}
|
||||
params = dict((k, v[0]) for k, v in parse_qs(parsed.query).items())
|
||||
params['name'] = unquote(parsed.path)[1:] # Unquote and strip leading /
|
||||
params['type'] = parsed.hostname
|
||||
if 'issuer' in params and not params['name'].startswith(params['issuer']):
|
||||
params['name'] = params['issuer'] + ':' + params['name']
|
||||
return params
|
||||
|
||||
|
||||
#
|
||||
# General utils
|
||||
#
|
||||
|
||||
def derive_key(salt, passphrase):
|
||||
if not passphrase:
|
||||
return None
|
||||
return PBKDF2(passphrase, salt, 16, 1000)
|
||||
|
||||
|
||||
def der_pack(*values):
|
||||
return b''.join([int2byte(t) + int2byte(len(v)) + v for t, v in zip(
|
||||
values[0::2], values[1::2])])
|
||||
|
||||
|
||||
def der_read(der_data, expected_t=None):
|
||||
t = byte2int(der_data[0])
|
||||
if expected_t is not None and expected_t != t:
|
||||
raise ValueError('Wrong tag. Expected: %x, got: %x' % (expected_t, t))
|
||||
l = byte2int(der_data[1])
|
||||
offs = 2
|
||||
if l > 0x80:
|
||||
n_bytes = l - 0x80
|
||||
l = b2len(der_data[offs:offs + n_bytes])
|
||||
offs = offs + n_bytes
|
||||
v = der_data[offs:offs + l]
|
||||
rest = der_data[offs + l:]
|
||||
if expected_t is None:
|
||||
return t, v, rest
|
||||
return v, rest
|
||||
|
||||
|
||||
def b2len(bs):
|
||||
l = 0
|
||||
for b in bs:
|
||||
l *= 256
|
||||
l += byte2int(b)
|
||||
return l
|
||||
|
||||
|
||||
def kill_scdaemon():
|
||||
try:
|
||||
# Works for Windows.
|
||||
from win32com.client import GetObject
|
||||
from win32api import OpenProcess, CloseHandle, TerminateProcess
|
||||
WMI = GetObject('winmgmts:')
|
||||
ps = WMI.InstancesOf('Win32_Process')
|
||||
for p in ps:
|
||||
if p.Properties_('Name').Value == 'scdaemon.exe':
|
||||
pid = p.Properties_('ProcessID').Value
|
||||
print("Killing", pid)
|
||||
handle = OpenProcess(1, False, pid)
|
||||
TerminateProcess(handle, -1)
|
||||
CloseHandle(handle)
|
||||
except ImportError:
|
||||
# Works for Linux and OS X.
|
||||
pids = subprocess.check_output(
|
||||
"ps ax | grep scdaemon | grep -v grep | awk '{ print $1 }'",
|
||||
shell=True).strip()
|
||||
if pids:
|
||||
for pid in pids.split():
|
||||
print("Killing", pid)
|
||||
subprocess.call(['kill', '-9', pid])
|
||||
|
||||
|
||||
NON_CCID_YK_PIDS = set([0x0110, 0x0113, 0x0114, 0x0401, 0x0402, 0x0403])
|
||||
|
||||
|
||||
def ccid_supported_but_disabled():
|
||||
"""
|
||||
Check whether the first connected YubiKey supports CCID,
|
||||
but has it disabled.
|
||||
"""
|
||||
|
||||
if sys.platform in ['win32', 'cygwin']:
|
||||
pids = _get_pids_win()
|
||||
elif sys.platform == 'darwin':
|
||||
pids = _get_pids_osx()
|
||||
else:
|
||||
pids = _get_pids_linux()
|
||||
|
||||
return bool(NON_CCID_YK_PIDS.intersection(pids))
|
||||
|
||||
|
||||
def _get_pids_linux():
|
||||
pid_pattern = re.compile(r' 1050:([0-9a-f]{4}) ')
|
||||
pids = []
|
||||
for line in subprocess.check_output('lsusb').splitlines():
|
||||
match = pid_pattern.search(line.decode('ascii'))
|
||||
if match:
|
||||
pids.append(int(match.group(1), 16))
|
||||
return pids
|
||||
|
||||
|
||||
def _get_pids_osx():
|
||||
pids = []
|
||||
vid_ok = False
|
||||
pid = None
|
||||
output = subprocess.check_output(['system_profiler', 'SPUSBDataType'])
|
||||
for line in output.splitlines():
|
||||
line = line.decode().strip()
|
||||
if line.endswith(':'): # New entry
|
||||
if vid_ok and pid is not None:
|
||||
pids.append(pid)
|
||||
vid_ok = False
|
||||
pid = None
|
||||
if line.startswith('Vendor ID: '):
|
||||
vid_ok = line.endswith(' 0x1050')
|
||||
elif line.startswith('Product ID:'):
|
||||
pid = int(line.rsplit(' ', 1)[1], 16)
|
||||
|
||||
return pids
|
||||
|
||||
|
||||
def _get_pids_win():
|
||||
pid_pattern = re.compile(r'PID_([0-9A-F]{4})')
|
||||
pids = []
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
output = subprocess.check_output(['wmic', 'path',
|
||||
'Win32_USBControllerDevice', 'get', '*'],
|
||||
startupinfo=startupinfo)
|
||||
for line in output.splitlines():
|
||||
if 'VID_1050' in line:
|
||||
match = pid_pattern.search(line.decode('ascii'))
|
||||
if match:
|
||||
pids.append(int(match.group(1), 16))
|
||||
return pids
|
@ -1,34 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
# Initialize resources (images, etc).
|
||||
try:
|
||||
from . import qt_resources
|
||||
assert qt_resources # Silence warnings about unused import
|
||||
except ImportError:
|
||||
print(
|
||||
"Could not import GUI resources. Run 'python setup.py"
|
||||
" qt_resources'.")
|
@ -1,285 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from PySide import QtGui, QtCore
|
||||
|
||||
from yubioath import __version__ as version
|
||||
from yubioath.gui.view.ccid_disabled import CcidDisabledDialog
|
||||
from yubioath.yubicommon import qt
|
||||
from ..cli.keystore import CONFIG_HOME
|
||||
|
||||
try:
|
||||
from ..core.legacy_otp import ykpers_version
|
||||
except ImportError:
|
||||
ykpers_version = 'None'
|
||||
from ..core.utils import kill_scdaemon
|
||||
from ..core.exc import NoSpaceError
|
||||
from . import messages as m
|
||||
from .controller import GuiController
|
||||
from .ccid import CardStatus
|
||||
from .view.systray import Systray
|
||||
from .view.codes import CodesWidget
|
||||
from .view.settings import SettingsDialog
|
||||
from .view.add_cred import AddCredDialog
|
||||
from .view.add_cred_legacy import AddCredDialog as AddCredLegacyDialog
|
||||
from .view.set_password import SetPasswordDialog
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
import argparse
|
||||
|
||||
|
||||
ABOUT_TEXT = """
|
||||
<h2>%s</h2>
|
||||
%s<br>
|
||||
%s
|
||||
<h4>%s</h4>
|
||||
%%s
|
||||
<br><br>
|
||||
""" % (m.app_name, m.copyright, m.version_1, m.libraries)
|
||||
|
||||
|
||||
class MainWidget(QtGui.QStackedWidget):
|
||||
|
||||
def __init__(self, controller):
|
||||
super(MainWidget, self).__init__()
|
||||
|
||||
self._controller = controller
|
||||
|
||||
self._build_ui()
|
||||
controller.refreshed.connect(self._refresh)
|
||||
controller.ccid_disabled.connect(self.ccid_disabled)
|
||||
controller.watcher.status_changed.connect(self._set_status)
|
||||
|
||||
def ccid_disabled(self):
|
||||
if not self._controller.mute_ccid_disabled_warning:
|
||||
dialog = CcidDisabledDialog()
|
||||
dialog.exec_()
|
||||
if dialog.do_not_ask_again.isChecked():
|
||||
self._controller.mute_ccid_disabled_warning = 1
|
||||
|
||||
def showEvent(self, event):
|
||||
event.accept()
|
||||
|
||||
def _build_ui(self):
|
||||
self.codes_widget = CodesWidget(self._controller)
|
||||
self.no_key_widget = QtGui.QLabel(m.no_key)
|
||||
self.no_key_widget.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.addWidget(self.no_key_widget)
|
||||
self.addWidget(self.codes_widget)
|
||||
|
||||
def _refresh(self):
|
||||
if self._controller.credentials is None:
|
||||
self.setCurrentIndex(0)
|
||||
else:
|
||||
self.setCurrentIndex(1)
|
||||
|
||||
def _set_status(self, status):
|
||||
if status == CardStatus.NoCard:
|
||||
self.no_key_widget.setText(m.no_key)
|
||||
elif status == CardStatus.InUse:
|
||||
self.no_key_widget.setText(m.key_busy)
|
||||
elif status == CardStatus.Present:
|
||||
self.no_key_widget.setText(m.key_present)
|
||||
|
||||
|
||||
class YubiOathApplication(qt.Application):
|
||||
|
||||
def __init__(self, args):
|
||||
super(YubiOathApplication, self).__init__(m, version)
|
||||
|
||||
QtCore.QCoreApplication.setOrganizationName(m.organization)
|
||||
QtCore.QCoreApplication.setOrganizationDomain(m.domain)
|
||||
QtCore.QCoreApplication.setApplicationName(m.app_name)
|
||||
|
||||
self.ensure_singleton()
|
||||
|
||||
self._widget = None
|
||||
self.settings = qt.Settings.wrap(
|
||||
os.path.join(CONFIG_HOME, 'settings.ini'),
|
||||
QtCore.QSettings.IniFormat)
|
||||
self._settings = self.settings.get_group('settings')
|
||||
|
||||
self._controller = GuiController(self, self._settings)
|
||||
|
||||
self._systray = Systray(self)
|
||||
|
||||
self._init_systray(args.tray or self._settings.get('systray', False))
|
||||
self._init_window(not args.tray)
|
||||
|
||||
def _init_systray(self, show=False):
|
||||
self._systray.setIcon(QtGui.QIcon(':/yubioath.png'))
|
||||
self._systray.setVisible(show)
|
||||
|
||||
def _init_window(self, show=True):
|
||||
self.window.setWindowTitle(m.win_title_1 % self.version)
|
||||
self.window.setWindowIcon(QtGui.QIcon(':/yubioath.png'))
|
||||
self.window.resize(self._settings.get('size', QtCore.QSize(320, 340)))
|
||||
|
||||
self._build_menu_bar()
|
||||
|
||||
self.window.showEvent = self._on_shown
|
||||
self.window.closeEvent = self._on_closed
|
||||
self.window.hideEvent = self._on_hide
|
||||
|
||||
if show:
|
||||
self.window.show()
|
||||
self.window.raise_()
|
||||
|
||||
def _build_menu_bar(self):
|
||||
file_menu = self.window.menuBar().addMenu(m.menu_file)
|
||||
self._add_action = QtGui.QAction(m.action_add, file_menu)
|
||||
self._add_action.triggered.connect(self._add_credential)
|
||||
file_menu.addAction(self._add_action)
|
||||
self._password_action = QtGui.QAction(m.action_password, file_menu)
|
||||
self._password_action.triggered.connect(self._change_password)
|
||||
self._password_action.setEnabled(False)
|
||||
file_menu.addAction(self._password_action)
|
||||
settings_action = QtGui.QAction(m.action_settings, file_menu)
|
||||
settings_action.triggered.connect(self._show_settings)
|
||||
file_menu.addAction(settings_action)
|
||||
file_menu.addSeparator()
|
||||
quit_action = QtGui.QAction(m.action_quit, file_menu)
|
||||
quit_action.triggered.connect(self._systray.quit)
|
||||
file_menu.addAction(quit_action)
|
||||
|
||||
if sys.platform == "darwin":
|
||||
close_action = QtGui.QAction(m.action_close, file_menu)
|
||||
close_action.setShortcut(QtGui.QKeySequence.Close)
|
||||
close_action.triggered.connect(self.window.hide)
|
||||
file_menu.addAction(close_action)
|
||||
|
||||
help_menu = self.window.menuBar().addMenu(m.menu_help)
|
||||
about_action = QtGui.QAction(m.action_about, help_menu)
|
||||
about_action.triggered.connect(self._about)
|
||||
help_menu.addAction(about_action)
|
||||
|
||||
self._controller.refreshed.connect(self._refresh_menu)
|
||||
|
||||
def _refresh_menu(self):
|
||||
enabled = bool(self._controller._reader)
|
||||
self._password_action.setEnabled(enabled)
|
||||
|
||||
def _on_shown(self, event):
|
||||
if self._settings.get('kill_scdaemon', False):
|
||||
kill_scdaemon()
|
||||
|
||||
if not self._widget:
|
||||
self._widget = MainWidget(self._controller)
|
||||
self.window.setCentralWidget(self._widget)
|
||||
self._controller.refresh_codes()
|
||||
event.accept()
|
||||
|
||||
def _on_hide(self, event):
|
||||
self._controller.forget_passwords()
|
||||
event.accept()
|
||||
|
||||
def _on_closed(self, event):
|
||||
self._settings['size'] = self.window.size()
|
||||
if self._systray.isVisible():
|
||||
# Unless move is called the position isn't saved!
|
||||
self.window.move(self.window.pos())
|
||||
self.window.hide()
|
||||
event.ignore()
|
||||
else:
|
||||
event.accept()
|
||||
|
||||
def _libversions(self):
|
||||
return 'ykpers: %s' % ykpers_version
|
||||
|
||||
def _about(self):
|
||||
QtGui.QMessageBox.about(
|
||||
self.window,
|
||||
m.about_1 % m.app_name,
|
||||
ABOUT_TEXT % (self.version, self._libversions()))
|
||||
|
||||
def _add_credential(self):
|
||||
c = self._controller.get_capabilities()
|
||||
if c.ccid:
|
||||
dialog = AddCredDialog(
|
||||
self.worker,
|
||||
c.version,
|
||||
self._controller.get_entry_names(),
|
||||
parent=self.window)
|
||||
if dialog.exec_():
|
||||
if not self._controller._reader:
|
||||
QtGui.QMessageBox.critical(
|
||||
self.window, m.key_removed, m.key_removed_desc)
|
||||
else:
|
||||
try:
|
||||
self._controller.add_cred(
|
||||
dialog.name,
|
||||
dialog.key,
|
||||
oath_type=dialog.oath_type,
|
||||
digits=dialog.n_digits,
|
||||
algo=dialog.algorithm,
|
||||
require_touch=dialog.require_touch)
|
||||
except NoSpaceError:
|
||||
QtGui.QMessageBox.critical(
|
||||
self.window, m.no_space, m.no_space_desc)
|
||||
elif c.otp:
|
||||
dialog = AddCredLegacyDialog(
|
||||
self.worker, c.otp, parent=self.window)
|
||||
if dialog.exec_():
|
||||
self._controller.add_cred_legacy(dialog.slot, dialog.key,
|
||||
dialog.touch)
|
||||
key = 'slot%d' % dialog.slot
|
||||
self._settings[key] = dialog.n_digits
|
||||
self._controller.refresh_codes()
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self.window, 'No key', 'No key')
|
||||
|
||||
def _change_password(self):
|
||||
dialog = SetPasswordDialog(self.window)
|
||||
if dialog.exec_():
|
||||
if not self._controller._reader:
|
||||
QtGui.QMessageBox.critical(
|
||||
self.window, m.key_removed, m.key_removed_desc)
|
||||
else:
|
||||
self._controller.set_password(dialog.password, dialog.remember)
|
||||
|
||||
def _show_settings(self):
|
||||
if SettingsDialog(self.window, self._settings).exec_():
|
||||
self._systray.setVisible(self._settings.get('systray', False))
|
||||
self._controller.settings_changed()
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Yubico Authenticator',
|
||||
add_help=True)
|
||||
parser.add_argument('-t', '--tray', action='store_true', help='starts '
|
||||
'the application minimized to the systray')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
app = YubiOathApplication(parse_args())
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,146 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from ..core.ccid import ScardDevice
|
||||
from smartcard import System
|
||||
from smartcard.ReaderMonitoring import ReaderMonitor, ReaderObserver
|
||||
from smartcard.CardMonitoring import CardMonitor, CardObserver
|
||||
from smartcard.Exceptions import SmartcardException
|
||||
from smartcard.pcsc.PCSCExceptions import EstablishContextException
|
||||
from PySide import QtCore
|
||||
import weakref
|
||||
|
||||
|
||||
class _CcidReaderObserver(ReaderObserver):
|
||||
|
||||
def __init__(self, controller):
|
||||
self._controller = weakref.ref(controller)
|
||||
self._monitor = ReaderMonitor()
|
||||
self._monitor.addObserver(self)
|
||||
|
||||
def update(self, observable, tup):
|
||||
(added, removed) = tup
|
||||
c = self._controller()
|
||||
if c:
|
||||
c._update(added, removed)
|
||||
|
||||
def delete(self):
|
||||
self._monitor.deleteObservers()
|
||||
|
||||
|
||||
class _CcidCardObserver(CardObserver):
|
||||
|
||||
def __init__(self, controller):
|
||||
self._controller = weakref.ref(controller)
|
||||
self._monitor = CardMonitor()
|
||||
self._monitor.addObserver(self)
|
||||
|
||||
def update(self, observable, tup):
|
||||
(added, removed) = tup
|
||||
c = self._controller()
|
||||
if c:
|
||||
c._update([card.reader for card in added],
|
||||
[r.reader for r in removed])
|
||||
|
||||
def delete(self):
|
||||
self._monitor.deleteObservers()
|
||||
|
||||
|
||||
class CardStatus:
|
||||
NoCard, InUse, Present = range(3)
|
||||
|
||||
|
||||
class CardWatcher(QtCore.QObject):
|
||||
status_changed = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, reader_name, callback, parent=None):
|
||||
super(CardWatcher, self).__init__(parent)
|
||||
|
||||
self._status = CardStatus.NoCard
|
||||
self._device = lambda: None
|
||||
self.reader_name = reader_name
|
||||
self._callback = callback or (lambda _: _)
|
||||
self._reader = None
|
||||
self._reader_observer = _CcidReaderObserver(self)
|
||||
self._card_observer = _CcidCardObserver(self)
|
||||
try:
|
||||
self._update(System.readers(), [])
|
||||
except EstablishContextException:
|
||||
pass # No PC/SC context!
|
||||
|
||||
def _update(self, added, removed):
|
||||
if self._reader in removed: # Device removed
|
||||
self.reader = None
|
||||
self._set_status(CardStatus.NoCard)
|
||||
|
||||
if self._reader is None:
|
||||
for reader in added:
|
||||
if self.reader_name in reader.name:
|
||||
self.reader = reader
|
||||
self._set_status(CardStatus.Present)
|
||||
return
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._status
|
||||
|
||||
def _set_status(self, value):
|
||||
if self._status != value:
|
||||
self._status = value
|
||||
self.status_changed.emit(value)
|
||||
|
||||
@property
|
||||
def reader(self):
|
||||
return self._reader
|
||||
|
||||
@reader.setter
|
||||
def reader(self, value):
|
||||
self._reader = value
|
||||
self._callback(self, value)
|
||||
|
||||
def open(self):
|
||||
dev = self._device()
|
||||
if dev is not None:
|
||||
return dev
|
||||
|
||||
if self._reader:
|
||||
conn = self._reader.createConnection()
|
||||
try:
|
||||
conn.connect()
|
||||
self._set_status(CardStatus.Present)
|
||||
dev = ScardDevice(conn)
|
||||
self._device = weakref.ref(dev)
|
||||
return dev
|
||||
except SmartcardException:
|
||||
self._set_status(CardStatus.InUse)
|
||||
|
||||
def __del__(self):
|
||||
self._reader_observer.delete()
|
||||
self._card_observer.delete()
|
||||
|
||||
|
||||
def observe_reader(reader_name='Yubikey', callback=None):
|
||||
return CardWatcher(reader_name, callback)
|
@ -1,176 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from .ccid import CardStatus
|
||||
from yubioath.yubicommon.compat import byte2int, int2byte
|
||||
from smartcard.scard import (
|
||||
SCardTransmit, SCardGetErrorMessage, SCardConnect, SCardDisconnect,
|
||||
SCardEstablishContext, SCardReleaseContext, SCardListReaders,
|
||||
SCARD_S_SUCCESS, SCARD_UNPOWER_CARD, SCARD_SCOPE_USER, SCARD_SHARE_SHARED,
|
||||
SCARD_PROTOCOL_T0, SCARD_PROTOCOL_T1
|
||||
)
|
||||
from PySide import QtCore
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
class LLScardDevice(object):
|
||||
|
||||
"""
|
||||
Low level pyscard based backend (Windows chokes on the high level one
|
||||
whenever you remove the key and re-insert it).
|
||||
"""
|
||||
|
||||
def __init__(self, context, card, protocol):
|
||||
self._context = context
|
||||
self._card = card
|
||||
self._protocol = protocol
|
||||
|
||||
def send_apdu(self, cl, ins, p1, p2, data):
|
||||
apdu = [cl, ins, p1, p2, len(data)] + [byte2int(b) for b in data]
|
||||
hresult, response = SCardTransmit(self._card, self._protocol, apdu)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise Exception('Failed to transmit: ' +
|
||||
SCardGetErrorMessage(hresult))
|
||||
status = response[-2] << 8 | response[-1]
|
||||
return b''.join(int2byte(i) for i in response[:-2]), status
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
SCardDisconnect(self._card, SCARD_UNPOWER_CARD)
|
||||
SCardReleaseContext(self._context)
|
||||
|
||||
|
||||
class PollerThread(threading.Thread):
|
||||
|
||||
def __init__(self, watcher):
|
||||
super(PollerThread, self).__init__()
|
||||
self._watcher = watcher
|
||||
self.daemon = True
|
||||
self.running = True
|
||||
|
||||
def run(self):
|
||||
old_readers = []
|
||||
while self.running:
|
||||
readers = self._list()
|
||||
added = [r for r in readers if r not in old_readers]
|
||||
removed = [r for r in old_readers if r not in readers]
|
||||
self._watcher._update(added, removed)
|
||||
old_readers = readers
|
||||
time.sleep(2)
|
||||
|
||||
def _list(self):
|
||||
try:
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise Exception('Failed to establish context : ' +
|
||||
SCardGetErrorMessage(hresult))
|
||||
|
||||
try:
|
||||
hresult, readers = SCardListReaders(hcontext, [])
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise Exception('Failed to list readers: ' +
|
||||
SCardGetErrorMessage(hresult))
|
||||
return readers
|
||||
finally:
|
||||
hresult = SCardReleaseContext(hcontext)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise Exception('Failed to release context: ' +
|
||||
SCardGetErrorMessage(hresult))
|
||||
except:
|
||||
return []
|
||||
|
||||
|
||||
class CardWatcher(QtCore.QObject):
|
||||
status_changed = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, reader_name, callback, parent=None):
|
||||
super(CardWatcher, self).__init__(parent)
|
||||
|
||||
self._status = CardStatus.NoCard
|
||||
self.reader_name = reader_name
|
||||
self._callback = callback or (lambda _: _)
|
||||
self._reader = None
|
||||
self._thread = PollerThread(self)
|
||||
self._thread.start()
|
||||
|
||||
def _update(self, added, removed):
|
||||
if self._reader in removed: # Device removed
|
||||
self.reader = None
|
||||
self._set_status(CardStatus.NoCard)
|
||||
|
||||
if self._reader is None:
|
||||
for reader in added:
|
||||
if self.reader_name in reader:
|
||||
self.reader = reader
|
||||
self._set_status(CardStatus.Present)
|
||||
return
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._status
|
||||
|
||||
def _set_status(self, value):
|
||||
if self._status != value:
|
||||
self._status = value
|
||||
self.status_changed.emit(value)
|
||||
|
||||
@property
|
||||
def reader(self):
|
||||
return self._reader
|
||||
|
||||
@reader.setter
|
||||
def reader(self, value):
|
||||
self._reader = value
|
||||
self._callback(self, value)
|
||||
|
||||
def open(self):
|
||||
if self._reader:
|
||||
try:
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise Exception('Failed to establish context : ' +
|
||||
SCardGetErrorMessage(hresult))
|
||||
|
||||
hresult, hcard, dwActiveProtocol = SCardConnect(
|
||||
hcontext, self._reader,
|
||||
SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise Exception('Unable to connect: ' +
|
||||
SCardGetErrorMessage(hresult))
|
||||
return LLScardDevice(hcontext, hcard, dwActiveProtocol)
|
||||
except Exception:
|
||||
self._set_status(CardStatus.InUse)
|
||||
|
||||
def __del__(self):
|
||||
self._thread.running = False
|
||||
self._thread.join()
|
||||
|
||||
|
||||
def observe_reader(reader_name='Yubikey', callback=None):
|
||||
return CardWatcher(reader_name, callback)
|
@ -1,425 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from ..core.standard import (YubiOathCcid, TYPE_HOTP)
|
||||
from ..core.controller import Controller
|
||||
from ..core.exc import CardError, DeviceLockedError
|
||||
from .ccid import CardStatus
|
||||
from yubioath.yubicommon.qt.utils import is_minimized
|
||||
from .view.get_password import GetPasswordDialog
|
||||
from .keystore import get_keystore
|
||||
from . import messages as m
|
||||
from yubioath.core.utils import ccid_supported_but_disabled
|
||||
from yubioath.yubicommon.qt import get_active_window, MutexLocker
|
||||
from PySide import QtCore, QtGui
|
||||
from time import time
|
||||
from collections import namedtuple
|
||||
|
||||
import sys
|
||||
if sys.platform == 'win32': # Windows has issues with the high level API.
|
||||
from .ccid_poll import observe_reader
|
||||
else:
|
||||
from .ccid import observe_reader
|
||||
|
||||
|
||||
Code = namedtuple('Code', 'code timestamp ttl')
|
||||
UNINITIALIZED = Code('', 0, 0)
|
||||
|
||||
TIME_PERIOD = 30
|
||||
INF = float('inf')
|
||||
|
||||
|
||||
class CredEntry(QtCore.QObject):
|
||||
changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, cred, controller):
|
||||
super(CredEntry, self).__init__()
|
||||
self.cred = cred
|
||||
self._controller = controller
|
||||
self._code = Code('', 0, 0)
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
return self._code
|
||||
|
||||
@code.setter
|
||||
def code(self, value):
|
||||
self._code = value
|
||||
self.changed.emit()
|
||||
|
||||
@property
|
||||
def manual(self):
|
||||
return self.cred.touch or self.cred.oath_type == TYPE_HOTP
|
||||
|
||||
def calculate(self):
|
||||
window = get_active_window()
|
||||
dialog = QtGui.QMessageBox(window)
|
||||
dialog.setWindowTitle(m.touch_title)
|
||||
dialog.setStandardButtons(QtGui.QMessageBox.NoButton)
|
||||
dialog.setIcon(QtGui.QMessageBox.Information)
|
||||
dialog.setText(m.touch_desc)
|
||||
timer = None
|
||||
|
||||
def cb(code):
|
||||
if timer:
|
||||
timer.stop()
|
||||
dialog.accept()
|
||||
if isinstance(code, Exception):
|
||||
QtGui.QMessageBox.warning(window, m.error,
|
||||
code.message)
|
||||
else:
|
||||
self.code = code
|
||||
self._controller._app.worker.post_bg((self._controller._calculate_cred,
|
||||
self.cred), cb)
|
||||
if self.cred.touch:
|
||||
dialog.exec_()
|
||||
elif self.cred.oath_type == TYPE_HOTP:
|
||||
# HOTP might require touch, we don't know. Assume yes after 500ms.
|
||||
timer = QtCore.QTimer(window)
|
||||
timer.setSingleShot(True)
|
||||
timer.timeout.connect(dialog.exec_)
|
||||
timer.start(500)
|
||||
|
||||
def delete(self):
|
||||
if self.cred.name in ['YubiKey slot 1', 'YubiKey slot 2']:
|
||||
self._controller.delete_cred_legacy(int(self.cred.name[-1]))
|
||||
else:
|
||||
self._controller.delete_cred(self.cred.name)
|
||||
|
||||
|
||||
Capabilities = namedtuple('Capabilities', 'ccid otp version')
|
||||
|
||||
|
||||
def names(creds):
|
||||
return set(c.cred.name for c in creds)
|
||||
|
||||
|
||||
class Timer(QtCore.QObject):
|
||||
time_changed = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, interval):
|
||||
super(Timer, self).__init__()
|
||||
self._interval = interval
|
||||
|
||||
now = time()
|
||||
rem = now % interval
|
||||
self._time = int(now - rem)
|
||||
QtCore.QTimer.singleShot((self._interval - rem) * 1000, self._tick)
|
||||
|
||||
def _tick(self):
|
||||
self._time += self._interval
|
||||
self.time_changed.emit(self._time)
|
||||
next_time = self._time + self._interval
|
||||
QtCore.QTimer.singleShot((next_time - time()) * 1000, self._tick)
|
||||
|
||||
@property
|
||||
def time(self):
|
||||
return self._time
|
||||
|
||||
|
||||
class GuiController(QtCore.QObject, Controller):
|
||||
refreshed = QtCore.Signal()
|
||||
ccid_disabled = QtCore.Signal()
|
||||
|
||||
def __init__(self, app, settings):
|
||||
super(GuiController, self).__init__()
|
||||
self._app = app
|
||||
self._settings = settings
|
||||
self._needs_read = False
|
||||
self._reader = None
|
||||
self._creds = None
|
||||
self._lock = QtCore.QMutex()
|
||||
self._keystore = get_keystore()
|
||||
self._current_device_has_ccid_disabled = False
|
||||
self.timer = Timer(TIME_PERIOD)
|
||||
|
||||
self.watcher = observe_reader(self.reader_name, self._on_reader)
|
||||
|
||||
self.startTimer(3000)
|
||||
self.timer.time_changed.connect(self.refresh_codes)
|
||||
|
||||
def settings_changed(self):
|
||||
self.watcher.reader_name = self.reader_name
|
||||
self.refresh_codes()
|
||||
|
||||
@property
|
||||
def reader_name(self):
|
||||
return self._settings.get('reader', 'Yubikey')
|
||||
|
||||
@property
|
||||
def slot1(self):
|
||||
return self._settings.get('slot1', 0)
|
||||
|
||||
@property
|
||||
def slot2(self):
|
||||
return self._settings.get('slot2', 0)
|
||||
|
||||
@property
|
||||
def mute_ccid_disabled_warning(self):
|
||||
return self._settings.get('mute_ccid_disabled_warning', 0)
|
||||
|
||||
@mute_ccid_disabled_warning.setter
|
||||
def mute_ccid_disabled_warning(self, value):
|
||||
self._settings['mute_ccid_disabled_warning'] = value
|
||||
|
||||
def unlock(self, std):
|
||||
if std.locked:
|
||||
key = self._keystore.get(std.id)
|
||||
if not key:
|
||||
self._app.worker.post_fg((self._init_std, std))
|
||||
return False
|
||||
std.unlock(key)
|
||||
return True
|
||||
|
||||
def grab_lock(self, lock=None, try_lock=False):
|
||||
return lock or MutexLocker(self._lock, False).lock(try_lock)
|
||||
|
||||
@property
|
||||
def otp_enabled(self):
|
||||
return self.otp_supported and bool(self.slot1 or self.slot2)
|
||||
|
||||
@property
|
||||
def credentials(self):
|
||||
return self._creds
|
||||
|
||||
def has_expiring(self, timestamp):
|
||||
for c in self._creds or []:
|
||||
if c.code.timestamp >= timestamp and c.code.ttl < INF:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_capabilities(self):
|
||||
assert self.grab_lock()
|
||||
ccid_dev = self.watcher.open()
|
||||
if ccid_dev:
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
return Capabilities(True, None, dev.version)
|
||||
legacy = self.open_otp()
|
||||
if legacy:
|
||||
return Capabilities(None, legacy.slot_status(), (0, 0, 0))
|
||||
return Capabilities(None, None, (0, 0, 0))
|
||||
|
||||
def get_entry_names(self):
|
||||
return names(self._creds)
|
||||
|
||||
def _on_reader(self, watcher, reader, lock=None):
|
||||
if reader:
|
||||
if self._reader is None:
|
||||
self._reader = reader
|
||||
self._creds = []
|
||||
if is_minimized(self._app.window):
|
||||
self._needs_read = True
|
||||
else:
|
||||
ccid_dev = watcher.open()
|
||||
if ccid_dev:
|
||||
std = YubiOathCcid(ccid_dev)
|
||||
self._app.worker.post_fg((self._init_std, std))
|
||||
else:
|
||||
self._needs_read = True
|
||||
elif self._needs_read:
|
||||
self.refresh_codes(self.timer.time)
|
||||
else:
|
||||
self._reader = None
|
||||
self._creds = None
|
||||
self.refreshed.emit()
|
||||
|
||||
def _init_std(self, std):
|
||||
lock = self.grab_lock()
|
||||
while std.locked:
|
||||
if self._keystore.get(std.id) is None:
|
||||
dialog = GetPasswordDialog(get_active_window())
|
||||
if dialog.exec_():
|
||||
self._keystore.put(std.id,
|
||||
std.calculate_key(dialog.password),
|
||||
dialog.remember)
|
||||
else:
|
||||
return
|
||||
try:
|
||||
std.unlock(self._keystore.get(std.id))
|
||||
except CardError:
|
||||
self._keystore.delete(std.id)
|
||||
self.refresh_codes(self.timer.time, lock, std)
|
||||
|
||||
def _await(self):
|
||||
self._creds = None
|
||||
|
||||
def wrap_credential(self, tup):
|
||||
(cred, code) = tup
|
||||
entry = CredEntry(cred, self)
|
||||
if code and code not in ['INVALID', 'TIMEOUT']:
|
||||
entry.code = Code(code, self.timer.time, TIME_PERIOD)
|
||||
|
||||
return entry
|
||||
|
||||
def _set_creds(self, creds):
|
||||
if creds:
|
||||
creds = [self.wrap_credential(c) for c in creds]
|
||||
if self._creds and names(creds) == names(self._creds):
|
||||
entry_map = dict((c.cred.name, c) for c in creds)
|
||||
for entry in self._creds:
|
||||
cred = entry.cred
|
||||
code = entry_map[cred.name].code
|
||||
if code.code:
|
||||
entry.code = code
|
||||
elif cred.oath_type != entry_map[cred.name].cred.oath_type:
|
||||
break
|
||||
else:
|
||||
return
|
||||
elif self._reader and self._needs_read and self._creds:
|
||||
return
|
||||
self._creds = creds
|
||||
self.refreshed.emit()
|
||||
|
||||
def _calculate_cred(self, cred):
|
||||
assert self.grab_lock()
|
||||
now = time()
|
||||
timestamp = self.timer.time
|
||||
if timestamp + TIME_PERIOD - now < 10:
|
||||
timestamp += TIME_PERIOD
|
||||
ttl = TIME_PERIOD
|
||||
if cred.oath_type == TYPE_HOTP:
|
||||
ttl = INF
|
||||
|
||||
if cred.name in ['YubiKey slot 1', 'YubiKey slot 2']:
|
||||
legacy = self.open_otp()
|
||||
if not legacy:
|
||||
raise ValueError('YubiKey removed!')
|
||||
|
||||
try:
|
||||
cred._legacy = legacy
|
||||
cred, code = super(GuiController, self).read_slot_otp(
|
||||
cred, timestamp, True)
|
||||
finally:
|
||||
cred._legacy = None # Release the handle.
|
||||
return Code(code, timestamp, TIME_PERIOD)
|
||||
|
||||
ccid_dev = self.watcher.open()
|
||||
if not ccid_dev:
|
||||
if self.watcher.status != CardStatus.Present:
|
||||
self._set_creds(None)
|
||||
return
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
if self.unlock(dev):
|
||||
return Code(dev.calculate(cred.name, cred.oath_type, timestamp),
|
||||
timestamp, ttl)
|
||||
|
||||
def read_slot_otp(self, cred, timestamp=None, use_touch=False):
|
||||
return super(GuiController, self).read_slot_otp(cred, timestamp, False)
|
||||
|
||||
def _refresh_codes_locked(self, timestamp=None, lock=None, std=None):
|
||||
if not std:
|
||||
device = self.watcher.open()
|
||||
else:
|
||||
device = std._device
|
||||
self._needs_read = bool(self._reader and device is None)
|
||||
timestamp = timestamp or self.timer.time
|
||||
try:
|
||||
creds = self.read_creds(device, self.slot1, self.slot2, timestamp,
|
||||
False)
|
||||
except DeviceLockedError:
|
||||
creds = []
|
||||
self._set_creds(creds)
|
||||
|
||||
def refresh_codes(self, timestamp=None, lock=None, std=None):
|
||||
if not self._reader and self.watcher.reader:
|
||||
return self._on_reader(self.watcher, self.watcher.reader, lock)
|
||||
elif is_minimized(self._app.window):
|
||||
self._needs_read = True
|
||||
return
|
||||
lock = self.grab_lock(lock, True)
|
||||
if not lock:
|
||||
return
|
||||
self._app.worker.post_bg((self._refresh_codes_locked, timestamp, lock,
|
||||
std))
|
||||
|
||||
def timerEvent(self, event):
|
||||
if not is_minimized(self._app.window):
|
||||
timestamp = self.timer.time
|
||||
if self._reader and self._needs_read:
|
||||
self.refresh_codes()
|
||||
elif self._reader is None:
|
||||
if self.otp_enabled:
|
||||
def refresh_otp():
|
||||
lock = self.grab_lock(try_lock=True)
|
||||
if lock:
|
||||
read = self.read_creds(
|
||||
None, self.slot1, self.slot2, timestamp, False)
|
||||
self._set_creds(read)
|
||||
self._app.worker.post_bg(refresh_otp)
|
||||
else:
|
||||
if ccid_supported_but_disabled():
|
||||
if not self._current_device_has_ccid_disabled:
|
||||
self.ccid_disabled.emit()
|
||||
self._current_device_has_ccid_disabled = True
|
||||
event.accept()
|
||||
return
|
||||
self._current_device_has_ccid_disabled = False
|
||||
event.accept()
|
||||
|
||||
def add_cred(self, *args, **kwargs):
|
||||
lock = self.grab_lock()
|
||||
ccid_dev = self.watcher.open()
|
||||
if ccid_dev:
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
if self.unlock(dev):
|
||||
super(GuiController, self).add_cred(dev, *args, **kwargs)
|
||||
self._creds = None
|
||||
self.refresh_codes(lock=lock)
|
||||
|
||||
def add_cred_legacy(self, *args, **kwargs):
|
||||
lock = self.grab_lock()
|
||||
super(GuiController, self).add_cred_legacy(*args, **kwargs)
|
||||
self._creds = None
|
||||
self.refresh_codes(lock=lock)
|
||||
|
||||
def delete_cred(self, name):
|
||||
lock = self.grab_lock()
|
||||
ccid_dev = self.watcher.open()
|
||||
if ccid_dev:
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
if self.unlock(dev):
|
||||
super(GuiController, self).delete_cred(dev, name)
|
||||
self._creds = None
|
||||
self.refresh_codes(lock=lock)
|
||||
|
||||
def delete_cred_legacy(self, *args, **kwargs):
|
||||
lock = self.grab_lock()
|
||||
super(GuiController, self).delete_cred_legacy(*args, **kwargs)
|
||||
self._creds = None
|
||||
self.refresh_codes(lock=lock)
|
||||
|
||||
def set_password(self, password, remember=False):
|
||||
assert self.grab_lock()
|
||||
ccid_dev = self.watcher.open()
|
||||
if ccid_dev:
|
||||
dev = YubiOathCcid(ccid_dev)
|
||||
if self.unlock(dev):
|
||||
key = super(GuiController, self).set_password(dev, password)
|
||||
self._keystore.put(dev.id, key, remember)
|
||||
|
||||
def forget_passwords(self):
|
||||
self._keystore.forget()
|
||||
self._set_creds([])
|
@ -1,55 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from ..cli.keystore import get_keystore as _get_keystore
|
||||
|
||||
|
||||
def get_keystore():
|
||||
return Keystore(_get_keystore())
|
||||
|
||||
|
||||
class Keystore(object):
|
||||
|
||||
def __init__(self, store):
|
||||
self._store = store
|
||||
self._data = {}
|
||||
|
||||
def get(self, id):
|
||||
return self._data.get(id) or self._store.get(id)
|
||||
|
||||
def put(self, id, key, remember=False):
|
||||
if remember:
|
||||
self._store.put(id, key)
|
||||
else:
|
||||
self._data[id] = key
|
||||
|
||||
def delete(self, id):
|
||||
if id in self._data:
|
||||
del self._data[id]
|
||||
self._store.delete(id)
|
||||
|
||||
def forget(self):
|
||||
self._data = {}
|
@ -1,143 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
"""
|
||||
Strings for Yubico Authenticator.
|
||||
|
||||
Note: String names must not start with underscore (_).
|
||||
|
||||
"""
|
||||
|
||||
organization = "Yubico"
|
||||
domain = "yubico.com"
|
||||
app_name = "Yubico Authenticator"
|
||||
win_title_1 = "Yubico Authenticator (%s)"
|
||||
about_1 = "About: %s"
|
||||
copyright = "Copyright © Yubico"
|
||||
libraries = "Library versions"
|
||||
version_1 = "Version: %s"
|
||||
wait = "Please wait..."
|
||||
error = "Error"
|
||||
menu_file = "&File"
|
||||
menu_help = "&Help"
|
||||
action_about = "&About"
|
||||
action_add = "&Add..."
|
||||
action_password = "Set/Change &password"
|
||||
action_settings = "&Settings"
|
||||
action_delete = "&Delete"
|
||||
action_show = "&Show credentials"
|
||||
action_close = "&Close Window"
|
||||
action_quit = "&Quit"
|
||||
password = "Password"
|
||||
settings = "Settings"
|
||||
advanced = "Advanced"
|
||||
search = "Search"
|
||||
pass_required = "Password required"
|
||||
remember = "Remember password"
|
||||
no_key = "Insert a YubiKey..."
|
||||
key_busy = "YubiKey already in use!"
|
||||
key_present = "YubiKey found. Reading..."
|
||||
key_removed = "YubiKey removed"
|
||||
key_removed_desc = "There was an error communicating with the device!"
|
||||
ykstd_slots = "YubiKey standard slots"
|
||||
enable_slot_1 = "Read from slot %d"
|
||||
n_digits = "Number of digits"
|
||||
enable_systray = "Show in system tray"
|
||||
kill_scdaemon = "Kill scdaemon on show"
|
||||
reader_name = "Card reader name"
|
||||
no_creds = "No credentials available"
|
||||
add_cred = "New credential"
|
||||
cred_name = "Credential name"
|
||||
cred_key = "Secret key (base32)"
|
||||
cred_type = "Credential type"
|
||||
cred_totp = "Time based (TOTP)"
|
||||
cred_hotp = "Counter based (HOTP)"
|
||||
algorithm = "Algorithm"
|
||||
invalid_name = "Invalid name"
|
||||
invalid_name_desc = "Name must be at least 3 characters"
|
||||
invalid_key = "Invalid key"
|
||||
invalid_key_desc = "Key must be base32 encoded"
|
||||
set_pass = "Set password"
|
||||
new_pass = "New password (blank for none)"
|
||||
ver_pass = "Verify new password"
|
||||
pass_mismatch = "Passwords do not match"
|
||||
pass_mismatch_desc = "Please enter the same password twice"
|
||||
touch_title = "Touch required"
|
||||
touch_desc = "Touch your YubiKey now"
|
||||
delete_title = "Confirm credential deletion"
|
||||
delete_desc_1 = """<span>Are you sure you want to delete the credential?</span>
|
||||
<br>
|
||||
This action cannot be undone.
|
||||
<br><br>
|
||||
<b>Delete credential: %s</b>
|
||||
"""
|
||||
slot = "YubiKey slot"
|
||||
slot_2 = "Slot %d (%s)"
|
||||
free = "free"
|
||||
in_use = "in use"
|
||||
require_touch = "Require touch"
|
||||
no_slot = "No slot chosen"
|
||||
no_slot_desc = "Please choose a slot to write the credential to"
|
||||
overwrite_slot = "Overwrite slot?"
|
||||
overwrite_slot_desc_1 = "This will overwrite the credential currently " \
|
||||
"stored in slot %d. This action cannot be undone."
|
||||
overwrite_entry = "Overwrite entry?"
|
||||
overwrite_entry_desc = "An entry with this username already exists.\n\nDo " \
|
||||
"you wish to overwrite it? This action cannot be undone."
|
||||
qr_scan = "Scan a QR code"
|
||||
qr_scanning = "Scanning for QR code..."
|
||||
qr_not_found = "QR code not found"
|
||||
qr_not_found_desc = "No usable QR code detected. Make sure the QR code is " \
|
||||
"fully visible on your primary screen and try again."
|
||||
qr_not_supported = "Credential not supported"
|
||||
qr_not_supported_desc = "This credential type is not supported for slot " \
|
||||
"based usage."
|
||||
qr_invalid_type = "Invalid OTP type"
|
||||
qr_invalid_type_desc = "Only TOTP and HOTP types are supported."
|
||||
qr_invalid_digits = "Invalid number of digits"
|
||||
qr_invalid_digits_desc = "An OTP may only contain 6 or 8 digits."
|
||||
qr_invalid_algo = "Unsupported algorithm"
|
||||
qr_invalid_algo_desc = "SHA1 and SHA256 are the only supported OTP " \
|
||||
"algorithms at this time."
|
||||
tt_slot_enabled_1 = "Check to calculate TOTP codes using the YubiKey " \
|
||||
"standard slot %d credential."
|
||||
tt_num_digits = "The number of digits to show for the credential."
|
||||
tt_systray = "When checked, display an icon in the systray, and leave the " \
|
||||
"application running there when closed."
|
||||
tt_kill_scdaemon = "Kills any running scdaemon process when the window is " \
|
||||
"shown. This is useful when using this application together with GnuPG " \
|
||||
"to avoid GnuPG locking the device."
|
||||
tt_reader_name = "Changes the default smartcard reader name to look for. " \
|
||||
"This can be used to target a specific YubiKey when multiple are used, " \
|
||||
"or to target an NFC reader."
|
||||
ccid_disabled = '<b>CCID (smart card capabilities) is disabled on the ' \
|
||||
'inserted YubiKey.</b><br><br>Without CCID enabled, you will only be ' \
|
||||
'able to store 2 credentials.<br><br>' \
|
||||
'<a href="%s">Learn how to enable CCID</a><br>'
|
||||
no_space = "No space available"
|
||||
no_space_desc = "There is not enough space to add another " \
|
||||
"credential on your device.\n\nTo create free space to add a " \
|
||||
"new credential, delete those you no longer need."
|
@ -1,340 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
"""
|
||||
Given a 2D matrix of pixel data from a QR code, this module will decode and
|
||||
return the data contained within. Note that error correction is not
|
||||
implemented, and the input will thus have to be without any errors. Only
|
||||
supports numeric, alphanumeric and byte encodings.
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
__all__ = ['decode_qr_data']
|
||||
|
||||
|
||||
def decode_qr_data(qr_data):
|
||||
"""Given a 2D matrix of QR data, returns the encoded string"""
|
||||
size = len(qr_data)
|
||||
version = (size - 17) // 4
|
||||
level = bits_to_int(qr_data[8][:2])
|
||||
mask = bits_to_int(qr_data[8][2:5]) ^ 0b101
|
||||
|
||||
read_mask = [x[:] for x in [[1]*size]*size]
|
||||
|
||||
# Verify/Remove alignment patterns
|
||||
remove_locator_patterns(qr_data, read_mask)
|
||||
remove_alignment_patterns(read_mask, version)
|
||||
remove_timing_patterns(read_mask)
|
||||
|
||||
if version >= 7: # QR Codes version 7 or larger have version info.
|
||||
remove_version_info(read_mask)
|
||||
|
||||
# Read and deinterleave
|
||||
buf = bits_to_bytes(read_bits(qr_data, read_mask, mask))
|
||||
buf = deinterleave(buf, INTERLEAVE_PARAMS[version][level])
|
||||
bits = bytes_to_bits(buf)
|
||||
|
||||
# Decode data
|
||||
buf = ''
|
||||
while bits:
|
||||
data, bits = parse_bits(bits, version)
|
||||
buf += data
|
||||
return buf
|
||||
|
||||
|
||||
LOCATOR_BOX = [
|
||||
[1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 1, 1, 1, 0, 1],
|
||||
[1, 0, 1, 1, 1, 0, 1],
|
||||
[1, 0, 1, 1, 1, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1]
|
||||
]
|
||||
|
||||
MASKS = [
|
||||
lambda x, y: (y+x) % 2 == 0,
|
||||
lambda x, y: y % 2 == 0,
|
||||
lambda x, y: x % 3 == 0,
|
||||
lambda x, y: (y+x) % 3 == 0,
|
||||
lambda x, y: (y//2 + x//3) % 2 == 0,
|
||||
lambda x, y: (y*x) % 2 + (y*x) % 3 == 0,
|
||||
lambda x, y: ((y*x) % 2 + (y*x) % 3) % 2 == 0,
|
||||
lambda x, y: ((y+x) % 2 + (y*x) % 3) % 2 == 0
|
||||
]
|
||||
|
||||
ALPHANUM = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'
|
||||
|
||||
EC_LEVELS = ['H', 'Q', 'M', 'L']
|
||||
|
||||
INTERLEAVE_PARAMS = [ # From ISO/IEC 18004:2006
|
||||
[], # H, Q, M, L
|
||||
[1 * (9,), 1 * (13,), 1 * (16,), 1 * (19,)], # Version 1
|
||||
[1 * (16,), 1 * (22,), 1 * (28,), 1 * (34,)],
|
||||
[2 * (13,), 2 * (17,), 1 * (44,), 1 * (55,)],
|
||||
[4 * (9,), 2 * (24,), 2 * (32,), 1 * (80,)],
|
||||
[2 * (11,) + 2 * (12,), 2 * (15,) + 2 * (16,), 2 * (43,), 1 * (108,)],
|
||||
[4 * (15,), 4 * (19,), 4 * (27,), 2 * (68,)],
|
||||
[4 * (13,) + 1 * (14,), 2 * (14,) + 4 * (15,), 4 * (31,), 2 * (78,)],
|
||||
[4 * (14,) + 2 * (15,), 4 * (18,) + 2 * (19,), 2 * (38,) + 2 * (39,), 2 * (97,)], # noqa: E501
|
||||
[4 * (12,) + 4 * (13,), 4 * (16,) + 4 * (17,), 3 * (36,) + 2 * (37,), 2 * (116,)], # noqa: E501
|
||||
[6 * (15,) + 2 * (16,), 6 * (19,) + 2 * (20,), 4 * (43,) + 1 * (44,), 2 * (68,) + 2 * (69,)], # noqa: E501
|
||||
[3 * (12,) + 8 * (13,), 4 * (22,) + 4 * (23,), 1 * (50,) + 4 * (51,), 4 * (81,)], # noqa: E501
|
||||
[7 * (14,) + 4 * (15,), 4 * (20,) + 6 * (21,), 6 * (36,) + 2 * (37,), 2 * (92,) + 2 * (93,)], # noqa: E501
|
||||
[12 * (11,) + 4 * (12,), 8 * (20,) + 4 * (21,), 8 * (37,) + 1 * (38,), 4 * (107,)], # noqa: E501
|
||||
[11 * (12,) + 5 * (13,), 11 * (16,) + 5 * (17,), 4 * (40,) + 5 * (41,), 3 * (115,) + 1 * (116,)], # noqa: E501
|
||||
[11 * (12,) + 7 * (13,), 5 * (24,) + 7 * (25,), 5 * (41,) + 5 * (42,), 5 * (87,) + 1 * (88,)], # noqa: E501
|
||||
[3 * (15,) + 13 * (16,), 15 * (19,) + 2 * (20,), 7 * (45,) + 3 * (46,), 5 * (98,) + 1 * (99,)], # noqa: E501
|
||||
[2 * (14,) + 17 * (15,), 1 * (22,) + 15 * (23,), 10 * (46,) + 1 * (47,), 1 * (107,) + 5 * (108,)], # noqa: E501
|
||||
[2 * (14,) + 19 * (15,), 17 * (22,) + 1 * (23,), 9 * (43,) + 4 * (44,), 5 * (120,) + 1 * (121,)], # noqa: E501
|
||||
[9 * (13,) + 16 * (14,), 17 * (21,) + 4 * (22,), 3 * (44,) + 11 * (45,), 3 * (113,) + 4 * (114,)], # noqa: E501
|
||||
[15 * (15,) + 10 * (16,), 15 * (24,) + 5 * (25,), 3 * (41,) + 13 * (42,), 3 * (107,) + 5 * (108,)], # noqa: E501
|
||||
[19 * (16,) + 6 * (17,), 17 * (22,) + 6 * (23,), 17 * (42,), 4 * (116,) + 4 * (117,)], # noqa: E501
|
||||
[34 * (13,), 7 * (24,) + 16 * (25,), 17 * (46,), 2 * (111,) + 7 * (112,)],
|
||||
[16 * (15,) + 14 * (16,), 11 * (24,) + 14 * (25,), 4 * (47,) + 14 * (48,), 4 * (121,) + 5 * (122,)], # noqa: E501
|
||||
[30 * (16,) + 2 * (17,), 11 * (24,) + 16 * (25,), 6 * (45,) + 14 * (46,), 6 * (117,) + 4 * (118,)], # noqa: E501
|
||||
[22 * (15,) + 13 * (16,), 7 * (24,) + 22 * (25,), 8 * (47,) + 13 * (48,), 8 * (106,) + 4 * (107,)], # noqa: E501
|
||||
[33 * (16,) + 4 * (17,), 28 * (22,) + 6 * (23,), 19 * (46,) + 4 * (47,), 10 * (114,) + 2 * (115,)], # noqa: E501
|
||||
[12 * (15,) + 28 * (16,), 8 * (23,) + 26 * (24,), 22 * (45,) + 3 * (46,), 8 * (122,) + 4 * (123,)], # noqa: E501
|
||||
[11 * (15,) + 31 * (16,), 4 * (24,) + 31 * (25,), 3 * (45,) + 23 * (46,), 3 * (117,) + 10 * (118,)], # noqa: E501
|
||||
[19 * (15,) + 26 * (16,), 1 * (23,) + 37 * (24,), 21 * (45,) + 7 * (46,), 7 * (116,) + 7 * (117,)], # noqa: E501
|
||||
[23 * (15,) + 25 * (16,), 15 * (24,) + 25 * (25,), 19 * (47,) + 10 * (48,), 5 * (115,) + 10 * (116,)], # noqa: E501
|
||||
[23 * (15,) + 28 * (16,), 42 * (24,) + 1 * (25,), 2 * (46,) + 29 * (47,), 13 * (115,) + 3 * (116,)], # noqa: E501
|
||||
[19 * (15,) + 35 * (16,), 10 * (24,) + 35 * (25,), 10 * (46,) + 23 * (47,), 17 * (115,)], # noqa: E501
|
||||
[11 * (15,) + 46 * (16,), 29 * (24,) + 19 * (25,), 14 * (46,) + 21 * (47,), 17 * (115,) + 1 * (116,)], # noqa: E501
|
||||
[59 * (16,) + 1 * (17,), 44 * (24,) + 7 * (25,), 14 * (46,) + 23 * (47,), 13 * (115,) + 6 * (116,)], # noqa: E501
|
||||
[22 * (15,) + 41 * (16,), 39 * (24,) + 14 * (25,), 12 * (47,) + 26 * (48,), 12 * (121,) + 7 * (122,)], # noqa: E501
|
||||
[2 * (15,) + 64 * (16,), 46 * (24,) + 10 * (25,), 6 * (47,) + 34 * (48,), 6 * (121,) + 14 * (122,)], # noqa: E501
|
||||
[24 * (15,) + 46 * (16,), 49 * (24,) + 10 * (25,), 29 * (46,) + 14 * (47,), 17 * (122,) + 4 * (123,)], # noqa: E501
|
||||
[42 * (15,) + 32 * (16,), 48 * (24,) + 14 * (25,), 13 * (46,) + 32 * (47,), 4 * (122,) + 18 * (123,)], # noqa: E501
|
||||
[10 * (15,) + 67 * (16,), 43 * (24,) + 22 * (25,), 40 * (47,) + 7 * (48,), 20 * (117,) + 4 * (118,)], # noqa: E501
|
||||
[20 * (15,) + 61 * (16,), 34 * (24,) + 34 * (25,), 18 * (47,) + 31 * (48,), 19 * (118,) + 6 * (119,)], # noqa: E501
|
||||
]
|
||||
|
||||
|
||||
ALIGNMENT_POSITIONS = [ # From ISO/IEC 18004:2006
|
||||
[],
|
||||
[],
|
||||
[18], # Version 2
|
||||
[22],
|
||||
[26],
|
||||
[30],
|
||||
[34],
|
||||
[6, 22, 38],
|
||||
[6, 24, 42],
|
||||
[6, 26, 46],
|
||||
[6, 28, 50],
|
||||
[6, 30, 54],
|
||||
[6, 32, 58],
|
||||
[6, 34, 62],
|
||||
[6, 26, 46, 66],
|
||||
[6, 26, 48, 70],
|
||||
[6, 26, 50, 74],
|
||||
[6, 30, 54, 78],
|
||||
[6, 30, 56, 82],
|
||||
[6, 30, 58, 86],
|
||||
[6, 34, 62, 90],
|
||||
[6, 28, 50, 72, 94],
|
||||
[6, 26, 50, 74, 98],
|
||||
[6, 30, 54, 78, 102],
|
||||
|
||||
[6, 28, 54, 80, 106], # Version 24
|
||||
[6, 32, 58, 84, 110],
|
||||
[6, 30, 58, 86, 114],
|
||||
[6, 34, 62, 90, 118],
|
||||
[6, 26, 50, 74, 98, 122],
|
||||
[6, 30, 54, 78, 102, 126],
|
||||
[6, 26, 52, 78, 104, 130],
|
||||
[6, 30, 56, 82, 108, 134],
|
||||
[6, 34, 60, 86, 112, 138],
|
||||
[6, 30, 58, 86, 114, 142],
|
||||
[6, 34, 62, 90, 118, 146],
|
||||
[6, 30, 54, 78, 102, 126, 150],
|
||||
[6, 24, 50, 76, 102, 128, 154],
|
||||
[6, 28, 54, 80, 106, 132, 158],
|
||||
[6, 32, 58, 84, 110, 136, 162],
|
||||
[6, 26, 54, 82, 110, 138, 166],
|
||||
[6, 30, 58, 86, 114, 142, 170]
|
||||
]
|
||||
|
||||
|
||||
def check_region(data, x, y, match):
|
||||
"""Compares a region to the given """
|
||||
w = len(match[0])
|
||||
for cy in range(len(match)):
|
||||
if match[cy] != data[y+cy][x:x+w]:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def zero_region(data, x, y, w, h):
|
||||
"""Fills a region with zeroes."""
|
||||
for by in range(y, y+h):
|
||||
line = data[by]
|
||||
data[by] = line[:x] + [0]*w + line[x+w:]
|
||||
|
||||
|
||||
def bits_to_int(bits):
|
||||
"""Convers a list of bits into an integer"""
|
||||
val = 0
|
||||
for bit in bits:
|
||||
val = (val << 1) | bit
|
||||
return val
|
||||
|
||||
|
||||
def bits_to_bytes(bits):
|
||||
"""Converts a list of bits into a string of bytes"""
|
||||
return ''.join([chr(bits_to_int(bits[i:i+8]))
|
||||
for i in range(0, len(bits), 8)])
|
||||
|
||||
|
||||
def bytes_to_bits(buf):
|
||||
"""Converts a string of bytes to a list of bits"""
|
||||
return [b >> i & 1 for b in map(ord, buf) for i in range(7, -1, -1)]
|
||||
|
||||
|
||||
def deinterleave(data, b_cap):
|
||||
"""De-interleaves the bytes from a QR code"""
|
||||
n_bufs = len(b_cap)
|
||||
bufs = []
|
||||
for _ in range(n_bufs):
|
||||
bufs.append([])
|
||||
b_i = 0
|
||||
for i in range(sum(b_cap)):
|
||||
b = data[i]
|
||||
while b_cap[b_i] <= len(bufs[b_i]):
|
||||
b_i = (b_i + 1) % n_bufs
|
||||
bufs[b_i].append(b)
|
||||
b_i = (b_i + 1) % n_bufs
|
||||
buf = ''
|
||||
for b in bufs:
|
||||
buf += ''.join(b)
|
||||
return buf
|
||||
|
||||
|
||||
def parse_bits(bits, version):
|
||||
"""
|
||||
Parses and decodes a TLV value from the given list of bits.
|
||||
Returns the parsed data and the remaining bits, if any.
|
||||
"""
|
||||
enc, bits = bits_to_int(bits[:4]), bits[4:]
|
||||
if enc == 0: # End of data.
|
||||
return '', []
|
||||
elif enc == 1: # Number
|
||||
n_l = 10 if version < 10 else 12 if version < 27 else 14
|
||||
l, bits = bits_to_int(bits[:n_l]), bits[n_l:]
|
||||
buf = ''
|
||||
while l > 0:
|
||||
if l >= 3:
|
||||
num, bits = bits_to_int(bits[:10]), bits[10:]
|
||||
elif l >= 2:
|
||||
num, bits = bits_to_int(bits[:7]), bits[7:]
|
||||
else:
|
||||
num, bits = bits_to_int(bits[:3]), bits[3:]
|
||||
buf += str(num)
|
||||
elif enc == 2: # Alphanumeric
|
||||
n_l = 9 if version < 10 else 11 if version < 27 else 13
|
||||
l, bits = bits_to_int(bits[:n_l]), bits[n_l:]
|
||||
buf = ''
|
||||
while l > 0:
|
||||
if l >= 2:
|
||||
num, bits = bits_to_int(bits[:11]), bits[11:]
|
||||
buf += ALPHANUM[num // 45]
|
||||
buf += ALPHANUM[num % 45]
|
||||
l -= 2
|
||||
else:
|
||||
num, bits = bits_to_int(bits[:6]), bits[6:]
|
||||
buf += ALPHANUM[num]
|
||||
l -= 1
|
||||
return buf, bits
|
||||
elif enc == 4: # Bytes
|
||||
n_l = 8 if version < 10 else 16
|
||||
l, bits = bits_to_int(bits[:n_l]), bits[n_l:]
|
||||
return bits_to_bytes(bits[:l*8]), bits[l*8:]
|
||||
else:
|
||||
raise ValueError('Unsupported encoding: %d' % enc)
|
||||
|
||||
|
||||
def remove_locator_patterns(data, mask):
|
||||
"""
|
||||
Verifies and blanks out the three large locator patterns and dedicated
|
||||
whitespace surrounding them.
|
||||
"""
|
||||
width = len(data)
|
||||
if not check_region(data, 0, 0, LOCATOR_BOX):
|
||||
raise ValueError('Top-left square missing')
|
||||
zero_region(mask, 0, 0, 9, 9)
|
||||
|
||||
if not check_region(data, width-7, 0, LOCATOR_BOX):
|
||||
raise ValueError('Top-right square missing')
|
||||
zero_region(mask, width-8, 0, 8, 9)
|
||||
|
||||
if not check_region(data, 0, width-7, LOCATOR_BOX):
|
||||
raise ValueError('Bottom-left square missing')
|
||||
zero_region(mask, 0, width-8, 9, 8)
|
||||
|
||||
|
||||
def remove_alignment_patterns(mask, version):
|
||||
"""Blanks out alignment patterns."""
|
||||
positions = ALIGNMENT_POSITIONS[version]
|
||||
for y in positions:
|
||||
for x in positions:
|
||||
# Do not try to remove patterns in locator pattern positions.
|
||||
if (x, y) not in [(6, 6), (6, positions[-1]), (positions[-1], 6)]:
|
||||
zero_region(mask, x-2, y-2, 5, 5)
|
||||
|
||||
|
||||
def remove_timing_patterns(mask):
|
||||
"""Blanks out tracking patterns."""
|
||||
width = len(mask)
|
||||
mask[6] = [0] * width
|
||||
for y in range(width):
|
||||
mask[y][6] = 0
|
||||
|
||||
|
||||
def remove_version_info(mask):
|
||||
"""Removes version data. Only for version 7 and greater."""
|
||||
width = len(mask)
|
||||
zero_region(mask, width-11, 0, 3, 6)
|
||||
zero_region(mask, 0, width-11, 5, 6)
|
||||
|
||||
|
||||
def read_bits(qr_data, read_mask, mask):
|
||||
"""Reads the data contained in a QR code as bits."""
|
||||
size = len(qr_data)
|
||||
mask_f = MASKS[mask]
|
||||
bits = []
|
||||
# Skip over vertical timing pattern
|
||||
for x in reversed(list(range(0, 6, 2)) + list(range(7, size, 2))):
|
||||
y_range = range(0, size)
|
||||
if (size - x) % 4 != 0:
|
||||
y_range = reversed(y_range)
|
||||
for y in y_range:
|
||||
for i in reversed(range(2)):
|
||||
if read_mask[y][x+i]:
|
||||
bits.append(qr_data[y][x+i] ^ mask_f(x+i, y))
|
||||
return bits
|
@ -1,187 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
"""
|
||||
Given an image, locates and parses the pixel data in QR codes.
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
from yubioath.yubicommon.compat import byte2int
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
__all__ = ['parse_qr_codes']
|
||||
|
||||
|
||||
Box = namedtuple('Box', ['x', 'y', 'w', 'h'])
|
||||
|
||||
|
||||
def is_dark(color): # If any R, G, or B value is < 200 we consider it dark.
|
||||
return any(byte2int(c) < 200 for c in color)
|
||||
|
||||
|
||||
def buffer_matches(matched):
|
||||
return len(matched) == 5 \
|
||||
and max(matched[:2] + matched[3:]) <= matched[2] // 2 \
|
||||
and min(matched[:2] + matched[3:]) >= matched[2] // 6
|
||||
|
||||
|
||||
def check_line(pixels):
|
||||
matching_dark = False
|
||||
matched = [0, 0, 0, 0, 0]
|
||||
for (i, pixel) in enumerate(pixels):
|
||||
if is_dark(pixel): # Dark pixel
|
||||
if matching_dark:
|
||||
matched[-1] += 1
|
||||
else:
|
||||
matched = matched[1:] + [1]
|
||||
matching_dark = True
|
||||
else: # Light pixel
|
||||
if not matching_dark:
|
||||
matched[-1] += 1
|
||||
else:
|
||||
if buffer_matches(matched):
|
||||
width = sum(matched)
|
||||
yield i - width, width
|
||||
matched = matched[1:] + [1]
|
||||
matching_dark = False
|
||||
|
||||
# Check final state of buffer
|
||||
if matching_dark and buffer_matches(matched):
|
||||
width = sum(matched)
|
||||
yield i - width, width
|
||||
|
||||
|
||||
def check_row(line, bpp, x_offs, x_width):
|
||||
return check_line([line[i*bpp:(i+1)*bpp]
|
||||
for i in range(x_offs, x_offs + x_width)])
|
||||
|
||||
|
||||
def check_col(image, bpp, x, y_offs, y_height):
|
||||
return check_line([image.scanLine(i)[x*bpp:(x+1)*bpp]
|
||||
for i in range(y_offs, y_offs + y_height)])
|
||||
|
||||
|
||||
def read_line(line, bpp, x_offs, x_width):
|
||||
matching_dark = not is_dark(line[x_offs*bpp:(x_offs+1)*bpp])
|
||||
matched = []
|
||||
for x in range(x_offs, x_offs + x_width):
|
||||
pixel = line[x*bpp:(x+1)*bpp]
|
||||
if is_dark(pixel): # Dark pixel
|
||||
if matching_dark:
|
||||
matched[-1] += 1
|
||||
else:
|
||||
matched.append(1)
|
||||
matching_dark = True
|
||||
else: # Light pixel
|
||||
if not matching_dark:
|
||||
matched[-1] += 1
|
||||
else:
|
||||
matched.append(1)
|
||||
matching_dark = False
|
||||
return matching_dark, matched
|
||||
|
||||
|
||||
def read_bits(image, bpp, img_x, img_y, img_w, img_h, size):
|
||||
qr_x_w = img_w / size
|
||||
qr_y_h = img_h / size
|
||||
qr_data = []
|
||||
for qr_y in range(size):
|
||||
y = img_y + int(qr_y_h / 2 + qr_y * qr_y_h)
|
||||
img_line = image.scanLine(y)
|
||||
qr_line = []
|
||||
for qr_x in range(size):
|
||||
x = img_x + int(qr_x_w / 2 + qr_x * qr_x_w)
|
||||
qr_line.append(is_dark(img_line[x * bpp:(x+1) * bpp]))
|
||||
qr_data.append(qr_line)
|
||||
return qr_data
|
||||
|
||||
|
||||
FINDER = [
|
||||
[True, True, True, True, True, True, True],
|
||||
[True, False, False, False, False, False, True],
|
||||
[True, False, True, True, True, False, True],
|
||||
[True, False, True, True, True, False, True],
|
||||
[True, False, True, True, True, False, True],
|
||||
[True, False, False, False, False, False, True],
|
||||
[True, True, True, True, True, True, True]
|
||||
]
|
||||
|
||||
|
||||
def parse_qr_codes(image, min_res=2):
|
||||
size = image.size()
|
||||
bpp = image.bytesPerLine() // size.width()
|
||||
|
||||
finders = locate_finders(image, min_res)
|
||||
|
||||
# Arrange finders into QR codes and extract data
|
||||
for (tl, tr, bl) in identify_groups(finders):
|
||||
min_x = min(tl.x, bl.x)
|
||||
min_y = min(tl.y, tr.y)
|
||||
width = tr.x + tr.w - min_x
|
||||
height = bl.y + bl.h - min_y
|
||||
|
||||
# Determine resolution by reading timing pattern
|
||||
line = image.scanLine(min_y + int(6.5 / 7 * max(tl.h, tr.h)))
|
||||
_, line_data = read_line(line, bpp, min_x, width)
|
||||
size = len(line_data) + 12
|
||||
|
||||
# Read QR code data
|
||||
yield read_bits(image, bpp, min_x, min_y, width, height, size)
|
||||
|
||||
|
||||
def locate_finders(image, min_res):
|
||||
size = image.size()
|
||||
bpp = image.bytesPerLine() // size.width()
|
||||
finders = set()
|
||||
for y in range(0, size.height(), min_res * 3):
|
||||
for (x, w) in check_row(image.scanLine(y), bpp, 0, size.width()):
|
||||
x_offs = x + w // 2
|
||||
y_offs = max(0, y - w)
|
||||
y_height = min(size.height() - y_offs, 2 * w)
|
||||
match = next(check_col(image, bpp, x_offs, y_offs, y_height), None)
|
||||
if match:
|
||||
(pos, h) = match
|
||||
y2 = y_offs + pos
|
||||
if read_bits(image, bpp, x, y2, w, h, 7) == FINDER:
|
||||
finders.add(Box(x, y2, w, h))
|
||||
return list(finders)
|
||||
|
||||
|
||||
def identify_groups(locators):
|
||||
# Find top left
|
||||
for tl in locators:
|
||||
x_tol = tl.w / 14
|
||||
y_tol = tl.h / 14
|
||||
|
||||
# Find top right
|
||||
for tr in locators:
|
||||
if tr.x > tl.x and abs(tl.y - tr.y) <= y_tol:
|
||||
|
||||
# Find bottom left
|
||||
for bl in locators:
|
||||
if bl.y > tl.y and abs(tl.x - bl.x) <= x_tol:
|
||||
yield tl, tr, bl
|
@ -1,25 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
@ -1,218 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from yubioath.yubicommon import qt
|
||||
from ...core.standard import ALG_SHA1, ALG_SHA256, TYPE_TOTP, TYPE_HOTP
|
||||
from ...core.utils import parse_uri
|
||||
from .. import messages as m
|
||||
from ..qrparse import parse_qr_codes
|
||||
from ..qrdecode import decode_qr_data
|
||||
from PySide import QtGui, QtCore
|
||||
from base64 import b32decode
|
||||
import re
|
||||
|
||||
NAME_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp(r'.{3,}'))
|
||||
|
||||
|
||||
class B32Validator(QtGui.QValidator):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(B32Validator, self).__init__(parent)
|
||||
self.partial = re.compile(r'^[ a-z2-7]+$', re.IGNORECASE)
|
||||
|
||||
def fixup(self, value):
|
||||
unpadded = value.upper().rstrip('=').replace(' ', '')
|
||||
return b32decode(unpadded + '=' * (-len(unpadded) % 8))
|
||||
|
||||
def validate(self, value, pos):
|
||||
try:
|
||||
self.fixup(value)
|
||||
return QtGui.QValidator.Acceptable
|
||||
except:
|
||||
if self.partial.match(value):
|
||||
return QtGui.QValidator.Intermediate
|
||||
return QtGui.QValidator.Invalid
|
||||
|
||||
|
||||
class AddCredDialog(qt.Dialog):
|
||||
|
||||
def __init__(self, worker, version, existing_entry_names, parent=None):
|
||||
super(AddCredDialog, self).__init__(parent)
|
||||
|
||||
self._worker = worker
|
||||
self._version = version
|
||||
self.setWindowTitle(m.add_cred)
|
||||
self._existing_entry_names = existing_entry_names
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self):
|
||||
layout = QtGui.QFormLayout(self)
|
||||
|
||||
self._qr_btn = QtGui.QPushButton(QtGui.QIcon(':/qr.png'), m.qr_scan)
|
||||
self._qr_btn.clicked.connect(self._scan_qr)
|
||||
layout.addRow(self._qr_btn)
|
||||
|
||||
self._cred_name = QtGui.QLineEdit()
|
||||
self._cred_name.setValidator(NAME_VALIDATOR)
|
||||
layout.addRow(m.cred_name, self._cred_name)
|
||||
|
||||
self._cred_key = QtGui.QLineEdit()
|
||||
self._cred_key.setValidator(B32Validator())
|
||||
layout.addRow(m.cred_key, self._cred_key)
|
||||
|
||||
layout.addRow(QtGui.QLabel(m.cred_type))
|
||||
self._cred_type = QtGui.QButtonGroup(self)
|
||||
self._cred_totp = QtGui.QRadioButton(m.cred_totp)
|
||||
self._cred_totp.setProperty('value', TYPE_TOTP)
|
||||
self._cred_type.addButton(self._cred_totp)
|
||||
layout.addRow(self._cred_totp)
|
||||
self._cred_hotp = QtGui.QRadioButton(m.cred_hotp)
|
||||
self._cred_hotp.setProperty('value', TYPE_HOTP)
|
||||
self._cred_type.addButton(self._cred_hotp)
|
||||
layout.addRow(self._cred_hotp)
|
||||
self._cred_totp.setChecked(True)
|
||||
|
||||
self._n_digits = QtGui.QComboBox()
|
||||
self._n_digits.addItems(['6', '8'])
|
||||
layout.addRow(m.n_digits, self._n_digits)
|
||||
|
||||
self._algorithm = QtGui.QComboBox()
|
||||
self._algorithm.addItems(['SHA-1', 'SHA-256'])
|
||||
layout.addRow(m.algorithm, self._algorithm)
|
||||
|
||||
self._require_touch = QtGui.QCheckBox(m.require_touch)
|
||||
# Touch-required support not available before 4.2.6
|
||||
if self._version >= (4, 2, 6):
|
||||
layout.addRow(self._require_touch)
|
||||
|
||||
btns = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
|
||||
QtGui.QDialogButtonBox.Cancel)
|
||||
btns.accepted.connect(self._save)
|
||||
btns.rejected.connect(self.reject)
|
||||
layout.addRow(btns)
|
||||
|
||||
def _do_scan_qr(self, qimage):
|
||||
for qr in parse_qr_codes(qimage):
|
||||
try:
|
||||
data = decode_qr_data(qr)
|
||||
if data.startswith('otpauth://'):
|
||||
return parse_uri(data)
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _scan_qr(self):
|
||||
winId = QtGui.QApplication.desktop().winId()
|
||||
qimage = QtGui.QPixmap.grabWindow(winId).toImage()
|
||||
self._worker.post(m.qr_scanning, (self._do_scan_qr, qimage),
|
||||
self._handle_qr)
|
||||
|
||||
def _handle_qr(self, parsed):
|
||||
if parsed:
|
||||
otp_type = parsed['type'].lower()
|
||||
n_digits = parsed.get('digits', '6')
|
||||
algo = parsed.get('algorithm', 'SHA1').upper()
|
||||
|
||||
if otp_type not in ['totp', 'hotp']:
|
||||
QtGui.QMessageBox.warning(
|
||||
self,
|
||||
m.qr_invalid_type,
|
||||
m.qr_invalid_type_desc)
|
||||
return
|
||||
if n_digits not in ['6', '8']:
|
||||
QtGui.QMessageBox.warning(
|
||||
self,
|
||||
m.qr_invalid_digits,
|
||||
m.qr_invalid_digits_desc)
|
||||
return
|
||||
if algo not in ['SHA1', 'SHA256']:
|
||||
# RFC6238 says SHA512 is also supported,
|
||||
# but it's not implemented here yet.
|
||||
QtGui.QMessageBox.warning(
|
||||
self,
|
||||
m.qr_invalid_algo,
|
||||
m.qr_invalid_algo_desc)
|
||||
return
|
||||
|
||||
self._cred_name.setText(parsed['name'])
|
||||
self._cred_key.setText(parsed['secret'])
|
||||
self._n_digits.setCurrentIndex(0 if n_digits == '6' else 1)
|
||||
self._algorithm.setCurrentIndex(0 if algo == 'SHA1' else 1)
|
||||
if otp_type == 'totp':
|
||||
self._cred_totp.setChecked(True)
|
||||
else:
|
||||
self._cred_hotp.setChecked(True)
|
||||
else:
|
||||
QtGui.QMessageBox.warning(
|
||||
self,
|
||||
m.qr_not_found,
|
||||
m.qr_not_found_desc)
|
||||
|
||||
def _entry_exists(self):
|
||||
return self._cred_name.text() in self._existing_entry_names
|
||||
|
||||
def _confirm_overwrite(self):
|
||||
return QtGui.QMessageBox.question(
|
||||
self, m.overwrite_entry, m.overwrite_entry_desc,
|
||||
QtGui.QMessageBox.Yes | QtGui.QMessageBox.Yes,
|
||||
QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes
|
||||
|
||||
def _save(self):
|
||||
if not self._cred_name.hasAcceptableInput():
|
||||
QtGui.QMessageBox.warning(
|
||||
self, m.invalid_name, m.invalid_name_desc)
|
||||
self._cred_name.selectAll()
|
||||
elif not self._cred_key.hasAcceptableInput():
|
||||
QtGui.QMessageBox.warning(self, m.invalid_key, m.invalid_key_desc)
|
||||
self._cred_key.selectAll()
|
||||
elif self._entry_exists() and not self._confirm_overwrite():
|
||||
self._cred_key.selectAll()
|
||||
else:
|
||||
self.accept()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._cred_name.text()
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self._cred_key.validator().fixup(self._cred_key.text())
|
||||
|
||||
@property
|
||||
def oath_type(self):
|
||||
return self._cred_type.checkedButton().property('value')
|
||||
|
||||
@property
|
||||
def n_digits(self):
|
||||
return int(self._n_digits.currentText())
|
||||
|
||||
@property
|
||||
def algorithm(self):
|
||||
return ALG_SHA1 if self._algorithm.currentIndex() == 0 else ALG_SHA256
|
||||
|
||||
@property
|
||||
def require_touch(self):
|
||||
return self._require_touch.isChecked()
|
@ -1,142 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from yubioath.yubicommon import qt
|
||||
from .add_cred import B32Validator
|
||||
from .. import messages as m
|
||||
from ..qrparse import parse_qr_codes
|
||||
from ..qrdecode import decode_qr_data
|
||||
from ...core.utils import parse_uri
|
||||
from PySide import QtGui
|
||||
from base64 import b32decode
|
||||
|
||||
|
||||
class AddCredDialog(qt.Dialog):
|
||||
|
||||
def __init__(self, worker, otp_slots=(0, 0), url=None, parent=None):
|
||||
super(AddCredDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle(m.add_cred)
|
||||
self._worker = worker
|
||||
self._slot_status = otp_slots
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self):
|
||||
layout = QtGui.QFormLayout(self)
|
||||
|
||||
self._qr_btn = QtGui.QPushButton(QtGui.QIcon(':/qr.png'), m.qr_scan)
|
||||
self._qr_btn.clicked.connect(self._scan_qr)
|
||||
layout.addRow(self._qr_btn)
|
||||
|
||||
self._cred_key = QtGui.QLineEdit()
|
||||
self._cred_key.setValidator(B32Validator())
|
||||
layout.addRow(m.cred_key, self._cred_key)
|
||||
|
||||
layout.addRow(QtGui.QLabel(m.slot))
|
||||
self._slot = QtGui.QButtonGroup(self)
|
||||
slot1_status = m.in_use if self._slot_status[0] else m.free
|
||||
self._slot_1 = QtGui.QRadioButton(m.slot_2 % (1, slot1_status))
|
||||
self._slot_1.setProperty('value', 1)
|
||||
self._slot.addButton(self._slot_1)
|
||||
layout.addRow(self._slot_1)
|
||||
slot2_status = m.in_use if self._slot_status[1] else m.free
|
||||
self._slot_2 = QtGui.QRadioButton(m.slot_2 % (2, slot2_status))
|
||||
self._slot_2.setProperty('value', 2)
|
||||
self._slot.addButton(self._slot_2)
|
||||
layout.addRow(self._slot_2)
|
||||
|
||||
self._touch = QtGui.QCheckBox(m.require_touch)
|
||||
layout.addRow(self._touch)
|
||||
|
||||
self._n_digits = QtGui.QComboBox()
|
||||
self._n_digits.addItems(['6', '8'])
|
||||
layout.addRow(m.n_digits, self._n_digits)
|
||||
|
||||
btns = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
|
||||
QtGui.QDialogButtonBox.Cancel)
|
||||
btns.accepted.connect(self._save)
|
||||
btns.rejected.connect(self.reject)
|
||||
layout.addRow(btns)
|
||||
|
||||
def _save(self):
|
||||
if not self._cred_key.hasAcceptableInput():
|
||||
QtGui.QMessageBox.warning(self, m.invalid_key, m.invalid_key_desc)
|
||||
self._cred_key.selectAll()
|
||||
elif not self._slot.checkedButton():
|
||||
QtGui.QMessageBox.warning(self, m.no_slot, m.no_slot_desc)
|
||||
elif self._slot_status[self.slot - 1] \
|
||||
and QtGui.QMessageBox.Ok != QtGui.QMessageBox.warning(
|
||||
self, m.overwrite_slot, m.overwrite_slot_desc_1 % self.slot,
|
||||
QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel):
|
||||
return
|
||||
else:
|
||||
self.accept()
|
||||
|
||||
def _do_scan_qr(self, qimage):
|
||||
for qr in parse_qr_codes(qimage):
|
||||
try:
|
||||
data = decode_qr_data(qr)
|
||||
if data.startswith('otpauth://'):
|
||||
return parse_uri(data)
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _scan_qr(self):
|
||||
winId = QtGui.QApplication.desktop().winId()
|
||||
qimage = QtGui.QPixmap.grabWindow(winId).toImage()
|
||||
self._worker.post(m.qr_scanning, (self._do_scan_qr, qimage),
|
||||
self._handle_qr)
|
||||
|
||||
def _handle_qr(self, parsed):
|
||||
if parsed:
|
||||
if parsed['type'] != 'totp':
|
||||
QtGui.QMessageBox.warning(self, m.qr_not_supported,
|
||||
m.qr_not_supported_desc)
|
||||
else:
|
||||
self._cred_key.setText(parsed['secret'])
|
||||
n_digits = parsed.get('digits', '6')
|
||||
self._n_digits.setCurrentIndex(0 if n_digits == '6' else 1)
|
||||
else:
|
||||
QtGui.QMessageBox.warning(
|
||||
self, m.qr_not_found, m.qr_not_found_desc)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
unpadded = self._cred_key.text().upper()
|
||||
return b32decode(unpadded + '=' * (-len(unpadded) % 8))
|
||||
|
||||
@property
|
||||
def slot(self):
|
||||
return self._slot.checkedButton().property('value')
|
||||
|
||||
@property
|
||||
def touch(self):
|
||||
return self._touch.isChecked()
|
||||
|
||||
@property
|
||||
def n_digits(self):
|
||||
return int(self._n_digits.currentText())
|
@ -1,24 +0,0 @@
|
||||
from PySide.QtGui import QCheckBox, QGridLayout, QLabel, \
|
||||
QDialogButtonBox
|
||||
from yubioath.gui.messages import ccid_disabled
|
||||
from yubioath.yubicommon.qt import Dialog
|
||||
|
||||
ENABLE_CCID_URL = 'http://yubi.co/modeswitch'
|
||||
|
||||
|
||||
class CcidDisabledDialog(Dialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(CcidDisabledDialog, self).__init__()
|
||||
|
||||
self.setWindowTitle("CCID disabled")
|
||||
self.do_not_ask_again = QCheckBox('Do not ask this again')
|
||||
label = QLabel(ccid_disabled % ENABLE_CCID_URL, openExternalLinks=True)
|
||||
layout = QGridLayout(self)
|
||||
layout.addWidget(label, 1, 1)
|
||||
layout.addWidget(self.do_not_ask_again, 2, 1)
|
||||
|
||||
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
|
||||
buttonBox.accepted.connect(self.close)
|
||||
|
||||
layout.addWidget(buttonBox, 3, 1)
|
@ -1,349 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from PySide import QtGui, QtCore
|
||||
from .. import messages as m
|
||||
from ...core.standard import TYPE_HOTP
|
||||
from yubioath.yubicommon.qt.utils import connect_once
|
||||
from time import time
|
||||
|
||||
|
||||
TIMELEFT_STYLE = """
|
||||
QProgressBar {
|
||||
padding: 1px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #2196f3;
|
||||
margin: 0px;
|
||||
width: 1px;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class TimeleftBar(QtGui.QProgressBar):
|
||||
expired = QtCore.Signal()
|
||||
|
||||
def __init__(self):
|
||||
super(TimeleftBar, self).__init__()
|
||||
|
||||
self.setStyleSheet(TIMELEFT_STYLE)
|
||||
self.setMaximumHeight(8)
|
||||
self.setInvertedAppearance(True)
|
||||
self.setRange(0, 30000)
|
||||
self.setValue(0)
|
||||
self.setTextVisible(False)
|
||||
|
||||
self._timer = 0
|
||||
self._timeleft = 0
|
||||
|
||||
def set_timeleft(self, millis):
|
||||
self._timeleft = max(0, millis)
|
||||
self.setValue(min(millis, self.maximum()))
|
||||
if self._timer == 0 and millis > 0:
|
||||
self._timer = self.startTimer(250)
|
||||
elif self._timer != 0 and millis <= 0:
|
||||
self.killTimer(self._timer)
|
||||
self._timer = 0
|
||||
|
||||
def timerEvent(self, event):
|
||||
self.set_timeleft(max(0, self._timeleft - 250))
|
||||
if self._timeleft == 0:
|
||||
self.expired.emit()
|
||||
|
||||
|
||||
class SearchBox(QtGui.QWidget):
|
||||
|
||||
def __init__(self, codes):
|
||||
super(SearchBox, self).__init__()
|
||||
|
||||
self._codeswidget = codes
|
||||
|
||||
layout = QtGui.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self._model = QtGui.QStringListModel()
|
||||
self._completer = QtGui.QCompleter()
|
||||
self._completer.setModel(self._model)
|
||||
self._completer.setCompletionMode(QtGui.QCompleter.InlineCompletion)
|
||||
self._completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
|
||||
self._lineedit = QtGui.QLineEdit()
|
||||
self._lineedit.setPlaceholderText(m.search)
|
||||
self._lineedit.setCompleter(self._completer)
|
||||
self._lineedit.textChanged.connect(self._text_changed)
|
||||
layout.addWidget(self._lineedit)
|
||||
|
||||
self._shortcut_focus = QtGui.QShortcut(
|
||||
QtGui.QKeySequence.Find,
|
||||
self._lineedit, self._set_focus)
|
||||
self._shortcut_clear = QtGui.QShortcut(
|
||||
QtGui.QKeySequence(self.tr("Esc")),
|
||||
self._lineedit, self._lineedit.clear)
|
||||
|
||||
self._timer = QtCore.QTimer()
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.setInterval(300)
|
||||
self._timer.timeout.connect(self._filter_changed)
|
||||
|
||||
def _set_focus(self):
|
||||
self._lineedit.setFocus()
|
||||
self._lineedit.selectAll()
|
||||
|
||||
def _text_changed(self, query):
|
||||
self._timer.stop()
|
||||
self._timer.start()
|
||||
|
||||
def _filter_changed(self):
|
||||
search_filter = self._lineedit.text()
|
||||
self._codeswidget.set_search_filter(search_filter)
|
||||
|
||||
def set_string_list(self, strings):
|
||||
self._model.setStringList(strings)
|
||||
|
||||
|
||||
class CodeMenu(QtGui.QMenu):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(CodeMenu, self).__init__(parent)
|
||||
self.entry = parent.entry
|
||||
|
||||
self.addAction(m.action_delete).triggered.connect(self._delete)
|
||||
|
||||
def _delete(self):
|
||||
res = QtGui.QMessageBox.warning(self, m.delete_title,
|
||||
m.delete_desc_1 % self.entry.cred.name,
|
||||
QtGui.QMessageBox.Ok,
|
||||
QtGui.QMessageBox.Cancel)
|
||||
if res == QtGui.QMessageBox.Ok:
|
||||
self.entry.delete()
|
||||
|
||||
|
||||
class Code(QtGui.QWidget):
|
||||
|
||||
def __init__(self, entry, timer, on_change):
|
||||
super(Code, self).__init__()
|
||||
self.entry = entry
|
||||
self.issuer, self.name = self._split_issuer_name()
|
||||
self._on_change = on_change
|
||||
self.entry.changed.connect(self._draw)
|
||||
self.timer = timer
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self._menu)
|
||||
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self):
|
||||
layout = QtGui.QHBoxLayout(self)
|
||||
labels = QtGui.QVBoxLayout()
|
||||
|
||||
if self.issuer:
|
||||
self._issuer_lbl = QtGui.QLabel(self.issuer)
|
||||
labels.addWidget(self._issuer_lbl)
|
||||
|
||||
self._code_lbl = QtGui.QLabel()
|
||||
labels.addWidget(self._code_lbl)
|
||||
|
||||
self._name_lbl = QtGui.QLabel(self.name)
|
||||
labels.addWidget(self._name_lbl)
|
||||
|
||||
layout.addLayout(labels)
|
||||
layout.addStretch()
|
||||
|
||||
self._calc_btn = QtGui.QPushButton(QtGui.QIcon(':/calc.png'), None)
|
||||
self._calc_btn.clicked.connect(self._calc)
|
||||
layout.addWidget(self._calc_btn)
|
||||
self._calc_btn.setVisible(self.entry.manual)
|
||||
|
||||
self._copy_btn = QtGui.QPushButton(QtGui.QIcon(':/copy.png'), None)
|
||||
self._copy_btn.clicked.connect(self._copy)
|
||||
layout.addWidget(self._copy_btn)
|
||||
|
||||
self.timer.time_changed.connect(self._draw)
|
||||
|
||||
self._draw()
|
||||
|
||||
@property
|
||||
def expired(self):
|
||||
code = self.entry.code
|
||||
return code.timestamp + code.ttl <= self.timer.time
|
||||
|
||||
def _draw(self):
|
||||
if self.expired:
|
||||
name_fmt = '<h2 style="color: gray;">%s</h2>'
|
||||
else:
|
||||
name_fmt = '<h2>%s</h2>'
|
||||
code = self.entry.code
|
||||
if self.entry.manual and self.entry.cred.oath_type != TYPE_HOTP:
|
||||
self._calc_btn.setEnabled(self.expired)
|
||||
self._code_lbl.setText(name_fmt % (code.code))
|
||||
self._copy_btn.setEnabled(bool(code.code))
|
||||
self._on_change()
|
||||
|
||||
def _copy(self):
|
||||
QtCore.QCoreApplication.instance().clipboard().setText(
|
||||
self.entry.code.code)
|
||||
|
||||
def _calc(self):
|
||||
if self.entry.manual:
|
||||
self._calc_btn.setDisabled(True)
|
||||
self.entry.calculate()
|
||||
if self.entry.cred.oath_type == TYPE_HOTP:
|
||||
QtCore.QTimer.singleShot(
|
||||
5000, lambda: self._calc_btn.setEnabled(True))
|
||||
|
||||
def _menu(self, pos):
|
||||
CodeMenu(self).popup(self.mapToGlobal(pos))
|
||||
|
||||
def _split_issuer_name(self):
|
||||
parts = self.entry.cred.name.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
return parts
|
||||
return None, self.entry.cred.name
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
if event.button() is QtCore.Qt.LeftButton:
|
||||
if (not self.entry.code.code or self.expired) and \
|
||||
self.entry.manual:
|
||||
def copy_close():
|
||||
self._copy()
|
||||
self.window().close()
|
||||
connect_once(self.entry.changed, copy_close)
|
||||
self.entry.calculate()
|
||||
else:
|
||||
self._copy() # TODO: Type code out with keyboard?
|
||||
self.window().close()
|
||||
event.accept()
|
||||
|
||||
|
||||
class CodesList(QtGui.QWidget):
|
||||
|
||||
def __init__(
|
||||
self, timer, credentials=[], on_change=None, search_filter=None):
|
||||
super(CodesList, self).__init__()
|
||||
|
||||
self._codes = []
|
||||
|
||||
layout = QtGui.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
for cred in credentials:
|
||||
if search_filter is not None and \
|
||||
search_filter.lower() not in cred.cred.name.lower():
|
||||
continue
|
||||
code = Code(cred, timer, on_change)
|
||||
layout.addWidget(code)
|
||||
self._codes.append(code)
|
||||
line = QtGui.QFrame()
|
||||
line.setFrameShape(QtGui.QFrame.HLine)
|
||||
line.setFrameShadow(QtGui.QFrame.Sunken)
|
||||
layout.addWidget(line)
|
||||
|
||||
if not credentials:
|
||||
no_creds = QtGui.QLabel(m.no_creds)
|
||||
no_creds.setAlignment(QtCore.Qt.AlignCenter)
|
||||
layout.addStretch()
|
||||
layout.addWidget(no_creds)
|
||||
layout.addStretch()
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def __del__(self):
|
||||
for code in self._codes:
|
||||
del code.entry
|
||||
del code
|
||||
|
||||
|
||||
class CodesWidget(QtGui.QWidget):
|
||||
|
||||
def __init__(self, controller):
|
||||
super(CodesWidget, self).__init__()
|
||||
|
||||
self._controller = controller
|
||||
controller.refreshed.connect(self.refresh)
|
||||
controller.timer.time_changed.connect(self.refresh_timer)
|
||||
|
||||
self._filter = None
|
||||
|
||||
self._build_ui()
|
||||
self.refresh()
|
||||
self.refresh_timer()
|
||||
|
||||
def _build_ui(self):
|
||||
layout = QtGui.QVBoxLayout(self)
|
||||
|
||||
self._timeleft = TimeleftBar()
|
||||
layout.addWidget(self._timeleft)
|
||||
|
||||
self._scroll_area = QtGui.QScrollArea()
|
||||
self._scroll_area.setWidgetResizable(True)
|
||||
self._scroll_area.setHorizontalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self._scroll_area.setVerticalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAsNeeded)
|
||||
self._scroll_area.setWidget(QtGui.QWidget())
|
||||
layout.addWidget(self._scroll_area)
|
||||
|
||||
self._searchbox = SearchBox(self)
|
||||
layout.addWidget(self._searchbox)
|
||||
|
||||
def refresh_timer(self, timestamp=None):
|
||||
if timestamp is None:
|
||||
timestamp = self._controller.timer.time
|
||||
if self._controller.has_expiring(timestamp):
|
||||
self._timeleft.set_timeleft(1000 * (timestamp + 30 - time()))
|
||||
else:
|
||||
self._timeleft.set_timeleft(0)
|
||||
|
||||
def rebuild_completions(self):
|
||||
creds = self._controller.credentials
|
||||
stringlist = set()
|
||||
if not creds:
|
||||
return
|
||||
for cred in creds:
|
||||
cred_name = cred.cred.name
|
||||
stringlist |= set(cred_name.split(':', 1))
|
||||
self._searchbox.set_string_list(list(stringlist))
|
||||
|
||||
def set_search_filter(self, search_filter):
|
||||
if len(search_filter) < 1:
|
||||
search_filter = None
|
||||
self._filter = search_filter
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
self._scroll_area.takeWidget().deleteLater()
|
||||
creds = self._controller.credentials
|
||||
self.rebuild_completions()
|
||||
self._scroll_area.setWidget(
|
||||
CodesList(
|
||||
self._controller.timer,
|
||||
creds or [],
|
||||
self.refresh_timer,
|
||||
self._filter))
|
||||
w = self._scroll_area.widget().minimumSizeHint().width()
|
||||
w += self._scroll_area.verticalScrollBar().width()
|
||||
self._scroll_area.setMinimumWidth(w)
|
@ -1,58 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from yubioath.yubicommon import qt
|
||||
from .. import messages as m
|
||||
from PySide import QtGui
|
||||
|
||||
|
||||
class GetPasswordDialog(qt.Dialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GetPasswordDialog, self).__init__(parent)
|
||||
self.setWindowTitle(m.pass_required)
|
||||
|
||||
layout = QtGui.QFormLayout(self)
|
||||
self._pwd_field = QtGui.QLineEdit()
|
||||
self._pwd_field.setEchoMode(QtGui.QLineEdit.Password)
|
||||
layout.addRow(m.password, self._pwd_field)
|
||||
|
||||
self._remember = QtGui.QCheckBox(m.remember)
|
||||
layout.addRow(self._remember)
|
||||
|
||||
btns = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
|
||||
QtGui.QDialogButtonBox.Cancel)
|
||||
btns.accepted.connect(self.accept)
|
||||
btns.rejected.connect(self.reject)
|
||||
layout.addWidget(btns)
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self._pwd_field.text()
|
||||
|
||||
@property
|
||||
def remember(self):
|
||||
return self._remember.isChecked()
|
@ -1,76 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from yubioath.yubicommon import qt
|
||||
from .. import messages as m
|
||||
from PySide import QtGui
|
||||
|
||||
|
||||
class SetPasswordDialog(qt.Dialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(SetPasswordDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle(m.set_pass)
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self):
|
||||
layout = QtGui.QFormLayout(self)
|
||||
|
||||
self._new_pass = QtGui.QLineEdit()
|
||||
self._new_pass.setEchoMode(QtGui.QLineEdit.Password)
|
||||
layout.addRow(m.new_pass, self._new_pass)
|
||||
|
||||
self._ver_pass = QtGui.QLineEdit()
|
||||
self._ver_pass.setEchoMode(QtGui.QLineEdit.Password)
|
||||
layout.addRow(m.ver_pass, self._ver_pass)
|
||||
|
||||
self._remember = QtGui.QCheckBox(m.remember)
|
||||
layout.addRow(self._remember)
|
||||
|
||||
btns = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
|
||||
QtGui.QDialogButtonBox.Cancel)
|
||||
btns.accepted.connect(self._save)
|
||||
btns.rejected.connect(self.reject)
|
||||
layout.addRow(btns)
|
||||
|
||||
def _save(self):
|
||||
if not self._new_pass.text() == self._ver_pass.text():
|
||||
self._new_pass.setText('')
|
||||
self._ver_pass.setText('')
|
||||
self._new_pass.setFocus()
|
||||
QtGui.QMessageBox.warning(self, m.pass_mismatch,
|
||||
m.pass_mismatch_desc)
|
||||
else:
|
||||
self.accept()
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self._new_pass.text()
|
||||
|
||||
@property
|
||||
def remember(self):
|
||||
return self._remember.isChecked()
|
@ -1,138 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from yubioath.yubicommon import qt
|
||||
from .. import messages as m
|
||||
from PySide import QtGui
|
||||
|
||||
INDENT = 16
|
||||
|
||||
|
||||
class SettingsDialog(qt.Dialog):
|
||||
|
||||
def __init__(self, parent, settings):
|
||||
super(SettingsDialog, self).__init__(parent)
|
||||
self.settings = settings
|
||||
|
||||
self.setWindowTitle(m.settings)
|
||||
self.accepted.connect(self._save)
|
||||
self._build_ui()
|
||||
self._reset()
|
||||
|
||||
def _build_ui(self):
|
||||
layout = QtGui.QFormLayout(self)
|
||||
layout.addRow(self.section(m.ykstd_slots))
|
||||
|
||||
# YubiKey slot 1
|
||||
self._slot1_enabled = QtGui.QCheckBox(m.enable_slot_1 % 1)
|
||||
self._slot1_enabled.setToolTip(m.tt_slot_enabled_1 % 1)
|
||||
layout.addRow(self._slot1_enabled)
|
||||
self._slot1_digits = QtGui.QComboBox()
|
||||
self._slot1_digits.addItems(['6', '8'])
|
||||
self._slot1_enabled.stateChanged.connect(self._slot1_digits.setEnabled)
|
||||
self._slot1_digits.setEnabled(False)
|
||||
self._slot1_digits.setToolTip(m.tt_num_digits)
|
||||
layout.addRow(m.n_digits, self._slot1_digits)
|
||||
layout.labelForField(self._slot1_digits).setIndent(INDENT)
|
||||
|
||||
# YubiKey slot 2
|
||||
self._slot2_enabled = QtGui.QCheckBox(m.enable_slot_1 % 2)
|
||||
self._slot2_enabled.setToolTip(m.tt_slot_enabled_1 % 2)
|
||||
layout.addRow(self._slot2_enabled)
|
||||
self._slot2_digits = QtGui.QComboBox()
|
||||
self._slot2_digits.addItems(['6', '8'])
|
||||
self._slot2_enabled.stateChanged.connect(self._slot2_digits.setEnabled)
|
||||
self._slot2_digits.setEnabled(False)
|
||||
self._slot2_digits.setToolTip(m.tt_num_digits)
|
||||
layout.addRow(m.n_digits, self._slot2_digits)
|
||||
layout.labelForField(self._slot2_digits).setIndent(INDENT)
|
||||
|
||||
layout.addRow(self.section(m.advanced))
|
||||
|
||||
# Systray
|
||||
self._systray = QtGui.QCheckBox(m.enable_systray)
|
||||
self._systray.setToolTip(m.tt_systray)
|
||||
layout.addRow(self._systray)
|
||||
|
||||
# Kill scdaemon
|
||||
self._kill_scdaemon = QtGui.QCheckBox(m.kill_scdaemon)
|
||||
self._kill_scdaemon.setToolTip(m.tt_kill_scdaemon)
|
||||
layout.addRow(self._kill_scdaemon)
|
||||
|
||||
# Reader name
|
||||
self._reader_name = QtGui.QLineEdit()
|
||||
self._reader_name.setToolTip(m.tt_reader_name)
|
||||
layout.addRow(m.reader_name, self._reader_name)
|
||||
|
||||
btns = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
|
||||
QtGui.QDialogButtonBox.Cancel)
|
||||
btns.accepted.connect(self.accept)
|
||||
btns.rejected.connect(self.reject)
|
||||
layout.addRow(btns)
|
||||
|
||||
def _reset(self):
|
||||
slot1 = self.settings.get('slot1', 0)
|
||||
self._slot1_digits.setCurrentIndex(1 if slot1 == 8 else 0)
|
||||
self._slot1_enabled.setChecked(bool(slot1))
|
||||
|
||||
slot2 = self.settings.get('slot2', 0)
|
||||
self._slot2_digits.setCurrentIndex(1 if slot2 == 8 else 0)
|
||||
self._slot2_enabled.setChecked(bool(slot2))
|
||||
|
||||
self._systray.setChecked(self.settings.get('systray', False))
|
||||
self._kill_scdaemon.setChecked(
|
||||
self.settings.get('kill_scdaemon', False))
|
||||
|
||||
self._reader_name.setText(self.settings.get('reader', 'Yubikey'))
|
||||
|
||||
@property
|
||||
def slot1(self):
|
||||
return self._slot1_enabled.isChecked() \
|
||||
and int(self._slot1_digits.currentText())
|
||||
|
||||
@property
|
||||
def slot2(self):
|
||||
return self._slot2_enabled.isChecked() \
|
||||
and int(self._slot2_digits.currentText())
|
||||
|
||||
@property
|
||||
def systray(self):
|
||||
return self._systray.isChecked()
|
||||
|
||||
@property
|
||||
def kill_scdaemon(self):
|
||||
return self._kill_scdaemon.isChecked()
|
||||
|
||||
@property
|
||||
def reader_name(self):
|
||||
return self._reader_name.text()
|
||||
|
||||
def _save(self):
|
||||
self.settings['slot1'] = self.slot1
|
||||
self.settings['slot2'] = self.slot2
|
||||
self.settings['systray'] = self.systray
|
||||
self.settings['kill_scdaemon'] = self.kill_scdaemon
|
||||
self.settings['reader'] = self.reader_name
|
@ -1,70 +0,0 @@
|
||||
# Copyright (c) 2014 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Additional permission under GNU GPL version 3 section 7
|
||||
#
|
||||
# If you modify this program, or any covered work, by linking or
|
||||
# combining it with the OpenSSL project's OpenSSL library (or a
|
||||
# modified version of that library), containing parts covered by the
|
||||
# terms of the OpenSSL or SSLeay licenses, We grant you additional
|
||||
# permission to convey the resulting work. Corresponding Source for a
|
||||
# non-source form of such a combination shall include the source code
|
||||
# for the parts of OpenSSL used as well as that of the covered work.
|
||||
|
||||
from PySide import QtGui
|
||||
from .. import messages as m
|
||||
import sys
|
||||
|
||||
|
||||
class Systray(QtGui.QSystemTrayIcon):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(Systray, self).__init__(parent)
|
||||
|
||||
self._reason = QtGui.QSystemTrayIcon.ActivationReason.Trigger
|
||||
|
||||
# Require double-click on OSX since single click opens menu.
|
||||
if sys.platform == 'darwin':
|
||||
self._reason = QtGui.QSystemTrayIcon.ActivationReason.DoubleClick
|
||||
|
||||
self.activated.connect(self._activated)
|
||||
self._build_menu()
|
||||
|
||||
def _build_menu(self):
|
||||
menu = QtGui.QMenu()
|
||||
|
||||
show_action = QtGui.QAction(m.action_show, menu)
|
||||
show_action.triggered.connect(self._show)
|
||||
menu.addAction(show_action)
|
||||
|
||||
quit_action = QtGui.QAction(m.action_quit, menu)
|
||||
quit_action.triggered.connect(self.quit)
|
||||
menu.addAction(quit_action)
|
||||
|
||||
self.setContextMenu(menu)
|
||||
|
||||
def _activated(self, reason):
|
||||
if reason == self._reason:
|
||||
self._show()
|
||||
|
||||
def _show(self):
|
||||
self.parent().window.show()
|
||||
self.parent().window.activateWindow()
|
||||
self.parent().window.raise_()
|
||||
|
||||
def quit(self):
|
||||
self.hide()
|
||||
self.parent().window.close()
|
@ -1 +0,0 @@
|
||||
../vendor/yubicommon/yubicommon
|