Compare commits

...

No commits in common. "master" and "pristine-tar" have entirely different histories.

62 changed files with 13 additions and 4805 deletions

26
COPYING
View File

@ -1,26 +0,0 @@
Copyright (c) 2010, 2011, 2012 Yubico AB
All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

View File

@ -1,8 +0,0 @@
include release.py
include COPYING
include NEWS
include ChangeLog
include examples/*
include util/*
include doc/*
recursive-include test *.py

33
NEWS
View File

@ -1,33 +0,0 @@
* Version 1.3.3 (released 2019-02-28)
** Fixes in Python 3 compatibility.
* Version 1.3.2 (released 2016-02-23)
** Various fixes to sequence number checking.
** Fix issue with using an access code with the debug flag on.
* Version 1.3.1 (released 2015-10-01)
** Fixup release to correct packages listed in last release.
* Version 1.3.0 (released 2015-10-01)
** Added Python 3 compatibility.
** Added the ability to zap a slot.
** Added support for YubiKey NEO.
** Added support for YubiKey 4.
* Version 1.2.3 (released 2015-03-23)
** Added PIDs for newer devices.
** Failure to call setConfiguration is now ignored.
* Version 1.2.2 (released 2015-02-11)
** Fixed bug in yubikey-totp using wrong timestamp.
** No longer require nose for setup, only for tests.
* Version 1.2.1 (released 2013-09-05)
** Improved Windows compatibility.
** Re-attach kernel driver if using PyUSB >= 1.0
* Version 1.2.0 (released 2013-08-07)
** Moved modules into root instead of Lib/.
* Version 1.1.1 (released 2013-08-05)
** Modified release procedure to simplify uploading to PyPI.

View File

@ -1,18 +0,0 @@
Metadata-Version: 1.2
Name: python-yubico
Version: 1.3.3
Summary: Python code for talking to Yubico's YubiKeys
Home-page: https://github.com/Yubico/python-yubico
Author: Dain Nilsson
Author-email: dain@yubico.com
Maintainer: Yubico Open Source Maintainers
Maintainer-email: ossmaint@yubico.com
License: BSD 2 clause
Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators

80
README
View File

@ -1,80 +0,0 @@
== python-yubico
Python package for talking to YubiKeys.
=== Introduction
The YubiKey is a hardware token for authentication. The main mode of the
YubiKey is entering a one time password (or a strong static password) by acting
as a USB HID device, but there are things one can do with bi-directional
communication:
1. Configuration. The yubikey_config class should be a feature-wise complete
implementation of everything that can be configured on YubiKeys version 1.3
to 3.x (besides deprecated functions in YubiKey 1.x).
See `examples/configure_nist_test_key` for an example.
2. Challenge-response. YubiKey 2.2 and later supports HMAC-SHA1 or Yubico
challenge-response operations.
See `examples/nist_challenge_response` for an example.
This library makes it easy to use these two features.
=== Example
Here is a trivial usage example :
[source, python]
----
#!/usr/bin/env python
""" Get version of connected YubiKey. """
import sys
import yubico
try:
yubikey = yubico.find_yubikey(debug=False)
print "Version : %s " % yubikey.version()
except yubico.yubico_exception.YubicoError as e:
print "ERROR: %s" % e.reason
sys.exit(1)
----
=== Installation
==== Using the Ubuntu/Debian package manager
If you use a recent Ubuntu release, you should be able to install python-yubico
with these commands :
$ sudo add-apt-repository ppa:yubico/stable
$ sudo apt-get update
$ sudo apt-get install python-yubico
The Launchpad PPA key generated for our packages is 32CBA1A9.
==== Using Pip
python-yubico is installable via pip:
$ pip install python-yubico
Or, directly from the source package in the standard Python way:
$ cd python-yubico-$ver
$ python setup.py install
This requires the `python-setuptools` package. You will also need
http://walac.github.io/pyusb[PyUSB], called python-usb in Debian/Ubuntu.
`pyusb` is available on PyPI and may be installed with pip: `pip install --pre
pyusb` The --pre command-line option indicates that pre-releases of `pyusb`
may also be searched (only pre-releases of `pyusb` are available on PyPI, and
pip skips pre-releases by default). Note that while both the 0.4 branch and the
1.0 branch are supported, the older 0.4 branch doesn't support re-attaching the
kernel device driver on close, which will leave the YubiKey in a state where it
is unable to output OTPs until it has been unplugged and plugged back in again.
==== On Windows
If you use Windows, you will require a PyUSB backend. Python-yubico has been
tested with http://libusbx.org[libusbx] and confirmed working, without the need
for replacing the device driver.
=== License
Copyright (c) Yubico AB.
Licensed under the BSD 2-clause license.
See the file COPYING for full licence statement.

23
debian/README.source vendored
View File

@ -1,23 +0,0 @@
We describe here one way to work with the package sources.
Initialize cowbuilder:
git-pbuilder create
Optionally update cowbuilder:
git-pbuilder update
Clone the repository:
git clone git@github.com:Yubico/python-yubico-dpkg.git
Build the package:
gbp buildpackage --git-pbuilder --git-pbuilder-options=--twice
If all goes well, you should have newly built packages in ../.
Update the package to a new upstream release (don't forget debian/changelog):
gbp import-orig /path/to/new-release.tar.gz

115
debian/changelog vendored
View File

@ -1,115 +0,0 @@
python-yubico (1.3.3-0.2.99) unstable; urgency=medium
* Non-maintainer upload.
* control: Drop python3-argparse from depends, it's provided by
libpython. (Closes: #951040)
-- Timo Aaltonen <tjaalton@debian.org> Tue, 31 Mar 2020 07:00:00 +0300
python-yubico (1.3.3-0.2) unstable; urgency=medium
* Non-maintainer upload.
* Drop python2 support; Closes: #938283
-- Sandro Tosi <morph@debian.org> Sun, 26 Jan 2020 21:30:44 -0500
python-yubico (1.3.3-0.1) unstable; urgency=medium
* New upstream release with Python3 compatibility fixes
* Drop python2 package (Closes: #938283)
-- Gianfranco Costamagna <locutusofborg@debian.org> Fri, 08 Nov 2019 11:24:23 +0100
python-yubico (1.3.2-2.2) unstable; urgency=medium
* Non-maintainer upload.
* Fix some lintian warnings, e.g. duplicate long description
* Fix nocheck not honoured in dh_auto_test
* Fix copyright non in secure mode
* Bump std-version to 4.4.0
* Bump compat level to 12
[ Darsey Litzenberger <dlitz@dlitz.net> ]
* Fixup previous upload Closes: #934861
-- Gianfranco Costamagna <locutusofborg@debian.org> Fri, 23 Aug 2019 10:45:57 +0200
python-yubico (1.3.2-2.1) unstable; urgency=medium
* Non-maintainer upload.
* Package python3-yubico, migrate to pybuild. (Closes: #891252)
-- Timo Aaltonen <tjaalton@debian.org> Thu, 08 Aug 2019 21:15:22 +0300
python-yubico (1.3.2-2) unstable; urgency=medium
* Acknowledge NMU.
* Move package from github to salsa, updating Vcs-* URLs.
-- Simon Josefsson <simon@josefsson.org> Thu, 21 Mar 2019 17:33:09 +0100
python-yubico (1.3.2-1.1) unstable; urgency=medium
* Non-maintainer upload.
* Drop obsoleted pycentral preinst. (Closes: #905663)
-- Harlan Lieberman-Berg <hlieberman@debian.org> Tue, 28 Aug 2018 01:30:16 -0400
python-yubico (1.3.2-1) unstable; urgency=medium
[ Dain Nilsson ]
* New upstream version,
[ Simon Josefsson ]
* Use https URLs in Vcs-Git.
* Update Standards-Version from 3.9.6 to 3.9.8.
* Move python-yubico-tools from 'python' section to 'utils'.
* Package description fixes.
-- Simon Josefsson <simon@josefsson.org> Wed, 10 Aug 2016 15:39:41 +0200
python-yubico (1.3.1-1) unstable; urgency=medium
[ Dain Nilsson ]
* New upstream version.
[ Simon Josefsson ]
* Add debian/gbp.conf.
-- Dain Nilsson <dain@yubico.com> Thu, 01 Oct 2015 16:03:57 +0200
python-yubico (1.2.3-1) unstable; urgency=medium
[ Dain Nilsson ]
* New upstream version.
* Re-packaged for easy PyPI releasing.
[ Simon Josefsson ]
* Acknowledge NMU.
* Change maintainer field to match other Yubico packages.
* Drop Fredrik as uploader.
* Bump compat to 9.
* Drop invalid Upstream-Contact email.
* Simplify README.source.
-- Dain Nilsson <dain@yubico.com> Mon, 24 Aug 2015 21:25:48 +0200
python-yubico (1.1.0-2.1) unstable; urgency=medium
* Non-maintainer upload.
* Convert from dh_pysupport to dh_python2 (Closes: #786162)
- replace python-support with dh-python and python-all in Build-Depends
-- Andrey Rahmatullin <wrar@debian.org> Thu, 20 Aug 2015 00:50:43 +0500
python-yubico (1.1.0-2) unstable; urgency=low
* Change Maintainer address to one that doesn't require subscription.
-- Fredrik Thulin <fredrik@yubico.com> Tue, 26 Jun 2012 12:56:37 +0200
python-yubico (1.1.0-1) unstable; urgency=low
* Initial release. (Closes: #676628)
-- Fredrik Thulin <fredrik@yubico.com> Fri, 08 Jun 2012 14:45:31 +0200

42
debian/control vendored
View File

@ -1,42 +0,0 @@
Source: python-yubico
Maintainer: Debian Authentication Maintainers <pkg-auth-maintainers@lists.alioth.debian.org>
Uploaders: Klas Lindfors <klas@yubico.com>, Dain Nilsson <dain@yubico.com>
Section: python
Priority: optional
Build-Depends: debhelper-compat (= 12),
dh-python,
python3-all (>= 2.6.6-3),
python3-pytest,
python3-setuptools (>= 0.6b3),
python3-usb,
Standards-Version: 4.4.0
Homepage: https://developers.yubico.com/python-yubico/
Vcs-Browser: https://salsa.debian.org/auth-team/python-yubico
Vcs-Git: https://salsa.debian.org/auth-team/python-yubico.git
Package: python-yubico-tools
Architecture: all
Section: utils
Depends: ${python3:Depends},
${misc:Depends},
python3-yubico (= ${binary:Version})
Provides: ${python3:Provides}
Description: Tools for Yubico YubiKeys
The YubiKey is a hardware authentication token. This package
contains utilities for the YubiKey implemented using the
python-yubico package.
.
This package currently includes the following utilities :
.
* yubikey-totp - OATH TOTP code generator using YubiKey
Package: python3-yubico
Architecture: all
Depends: ${python3:Depends},
${misc:Depends},
python3-usb
Provides: ${python3:Provides}
Description: Python3 library for talking to Yubico YubiKeys
The YubiKey is a hardware authentication token. This is a Python3
library for interacting with YubiKeys. Typical use is to detect,
configure (personalize) or issue challenge-responses to YubiKeys.

32
debian/copyright vendored
View File

@ -1,32 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: python-yubico
Source: https://developers.yubico.com/python-yubico/
Files: *
Copyright: Copyright (c) 2009-2013 Yubico AB
License: BSD-2-clause
All rights reserved.
.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

3
debian/gbp.conf vendored
View File

@ -1,3 +0,0 @@
[DEFAULT]
pristine-tar = True
sign-tags = True

View File

@ -1,8 +0,0 @@
--- a/util/yubikey-totp
+++ b/util/yubikey-totp
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (c) 2011, Yubico AB
# See the file COPYING for licence statement.

View File

@ -1 +0,0 @@
py3k_shebang.patch

View File

@ -1 +0,0 @@
util/yubikey-totp usr/bin/

View File

@ -1 +0,0 @@
util/yubi*.1

View File

@ -1 +0,0 @@
README

View File

@ -1 +0,0 @@
examples/*

16
debian/rules vendored
View File

@ -1,16 +0,0 @@
#!/usr/bin/make -f
export PYBUILD_NAME=yubico
override_dh_installchangelogs:
dh_installchangelogs ChangeLog
override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
# nosetests3 -e test_challenge_response -e test_serial -e test_status
pytest-3 -k 'not test_challenge_response and not test_serial and not test_status'
endif
%:
dh $@ --with python3 --buildsystem=pybuild

View File

@ -1 +0,0 @@
3.0 (quilt)

View File

@ -1 +0,0 @@
extend-diff-ignore=python_yubico.egg-info/*

View File

@ -1,98 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1
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
1V2YwD9DDxVIc6akgO8G3SOvetZ5LQ==
=xJ7j
-----END PGP PUBLIC KEY BLOCK-----

3
debian/watch vendored
View File

@ -1,3 +0,0 @@
version=3
opts=pgpsigurlmangle=s/$/.sig/ \
https://developers.yubico.com/python-yubico/Releases/ python-yubico-(\d[\d.]*)\.tar\.gz

View File

@ -1,220 +0,0 @@
/* -*- mode:C; c-file-style: "bsd" -*- */
/*****************************************************************************************
** **
** Y K D E F - Common Yubikey project header **
** **
** Date / Rev / Sign / Remark **
** 06-06-03 / 0.9.0 / J E / Main **
** 06-08-25 / 1.0.0 / J E / Rewritten for final spec **
** 08-06-03 / 1.3.0 / J E / Added static OTP feature **
** 09-06-02 / 2.0.0 / J E / Added version 2 flags **
** 09-09-23 / 2.1.0 / J E / Added version 2.1 flags (OATH-HOTP) **
** 10-05-01 / 2.2.0 / J E / Added support for 2.2 ext. + frame **
** 11-04-15 / 2.3.0 / J E / Added support for 2.3 extensions **
** **
*****************************************************************************************/
#ifndef __YKDEF_H_INCLUDED__
#define __YKDEF_H_INCLUDED__
/* We need the structures defined here to be packed byte-wise */
#if defined(_WIN32) || defined(__GNUC__)
#pragma pack(push, 1)
#endif
/* USB Identity */
#define YUBICO_VID 0x1050
#define YUBIKEY_PID 0x0010
/* Slot entries */
#define SLOT_CONFIG 1 /* First (default / V1) configuration */
#define SLOT_NAV 2 /* V1 only */
#define SLOT_CONFIG2 3 /* Second (V2) configuration */
#define SLOT_UPDATE1 4 /* Update slot 1 */
#define SLOT_UPDATE2 5 /* Update slot 2 */
#define SLOT_SWAP 6 /* Swap slot 1 and 2 */
#define SLOT_DEVICE_SERIAL 0x10 /* Device serial number */
#define SLOT_CHAL_OTP1 0x20 /* Write 6 byte challenge to slot 1, get Yubico OTP response */
#define SLOT_CHAL_OTP2 0x28 /* Write 6 byte challenge to slot 2, get Yubico OTP response */
#define SLOT_CHAL_HMAC1 0x30 /* Write 64 byte challenge to slot 1, get HMAC-SHA1 response */
#define SLOT_CHAL_HMAC2 0x38 /* Write 64 byte challenge to slot 2, get HMAC-SHA1 response */
#define RESP_ITEM_MASK 0x07 /* Mask for slice item # in responses */
#define RESP_TIMEOUT_WAIT_MASK 0x1f /* Mask to get timeout value */
#define RESP_TIMEOUT_WAIT_FLAG 0x20 /* Waiting for timeout operation - seconds left in lower 5 bits */
#define RESP_PENDING_FLAG 0x40 /* Response pending flag */
#define SLOT_WRITE_FLAG 0x80 /* Write flag - set by app - cleared by device */
#define DUMMY_REPORT_WRITE 0x8f /* Write a dummy report to force update or abort */
#define SHA1_MAX_BLOCK_SIZE 64 /* Max size of input SHA1 block */
#define SHA1_DIGEST_SIZE 20 /* Size of SHA1 digest = 160 bits */
#define SERIAL_NUMBER_SIZE 4 /* Size of device serial number */
/* Frame structure */
#define SLOT_DATA_SIZE 64
struct frame_st {
unsigned char payload[SLOT_DATA_SIZE]; /* Frame payload */
unsigned char slot; /* Slot # field */
unsigned short crc; /* CRC field */
unsigned char filler[3]; /* Filler */
};
/* Ticket structure */
#define UID_SIZE 6 /* Size of secret ID field */
struct ticket_st {
unsigned char uid[UID_SIZE]; /* Unique (secret) ID */
unsigned short useCtr; /* Use counter (incremented by 1 at first use after power up) + usage flag in msb */
unsigned short tstpl; /* Timestamp incremented by approx 8Hz (low part) */
unsigned char tstph; /* Timestamp (high part) */
unsigned char sessionCtr; /* Number of times used within session. 0 for first use. After it wraps from 0xff to 1 */
unsigned short rnd; /* Pseudo-random value */
unsigned short crc; /* CRC16 value of all fields */
};
/* Activation modifier of sessionUse field (bitfields not uses as they are not portable) */
#define TICKET_ACT_HIDRPT 0x8000 /* Ticket generated at activation by keyboard (scroll/num/caps) */
#define TICKET_CTR_MASK 0x7fff /* Mask for useCtr value (except HID flag) */
/* Configuration structure */
#define FIXED_SIZE 16 /* Max size of fixed field */
#define KEY_SIZE 16 /* Size of AES key */
#define KEY_SIZE_OATH 20 /* Size of OATH-HOTP key (key field + first 4 of UID field) */
#define ACC_CODE_SIZE 6 /* Size of access code to re-program device */
struct config_st {
unsigned char fixed[FIXED_SIZE];/* Fixed data in binary format */
unsigned char uid[UID_SIZE]; /* Fixed UID part of ticket */
unsigned char key[KEY_SIZE]; /* AES key */
unsigned char accCode[ACC_CODE_SIZE]; /* Access code to re-program device */
unsigned char fixedSize; /* Number of bytes in fixed field (0 if not used) */
unsigned char extFlags; /* Extended flags - YubiKey 2.? and above */
unsigned char tktFlags; /* Ticket configuration flags */
unsigned char cfgFlags; /* General configuration flags */
unsigned char rfu[2]; /* Reserved for future use */
unsigned short crc; /* CRC16 value of all fields */
};
/* Ticket flags **************************************************************/
/* Yubikey 1 and above */
#define TKTFLAG_TAB_FIRST 0x01 /* Send TAB before first part */
#define TKTFLAG_APPEND_TAB1 0x02 /* Send TAB after first part */
#define TKTFLAG_APPEND_TAB2 0x04 /* Send TAB after second part */
#define TKTFLAG_APPEND_DELAY1 0x08 /* Add 0.5s delay after first part */
#define TKTFLAG_APPEND_DELAY2 0x10 /* Add 0.5s delay after second part */
#define TKTFLAG_APPEND_CR 0x20 /* Append CR as final character */
/* Yubikey 2 and above */
#define TKTFLAG_PROTECT_CFG2 0x80 /* Block update of config 2 unless config 2 is configured and has this bit set */
/* Configuration flags *******************************************************/
/* Yubikey 1 and above */
#define CFGFLAG_SEND_REF 0x01 /* Send reference string (0..F) before data */
#define CFGFLAG_PACING_10MS 0x04 /* Add 10ms intra-key pacing */
#define CFGFLAG_PACING_20MS 0x08 /* Add 20ms intra-key pacing */
#define CFGFLAG_STATIC_TICKET 0x20 /* Static ticket generation */
/* Yubikey 1 only */
#define CFGFLAG_TICKET_FIRST 0x02 /* Send ticket first (default is fixed part) */
#define CFGFLAG_ALLOW_HIDTRIG 0x10 /* Allow trigger through HID/keyboard */
/* Yubikey 2 and above */
#define CFGFLAG_SHORT_TICKET 0x02 /* Send truncated ticket (half length) */
#define CFGFLAG_STRONG_PW1 0x10 /* Strong password policy flag #1 (mixed case) */
#define CFGFLAG_STRONG_PW2 0x40 /* Strong password policy flag #2 (subtitute 0..7 to digits) */
#define CFGFLAG_MAN_UPDATE 0x80 /* Allow manual (local) update of static OTP */
/* Yubikey 2.1 and above */
#define TKTFLAG_OATH_HOTP 0x40 /* OATH HOTP mode */
#define CFGFLAG_OATH_HOTP8 0x02 /* Generate 8 digits HOTP rather than 6 digits */
#define CFGFLAG_OATH_FIXED_MODHEX1 0x10 /* First byte in fixed part sent as modhex */
#define CFGFLAG_OATH_FIXED_MODHEX2 0x40 /* First two bytes in fixed part sent as modhex */
#define CFGFLAG_OATH_FIXED_MODHEX 0x50 /* Fixed part sent as modhex */
#define CFGFLAG_OATH_FIXED_MASK 0x50 /* Mask to get out fixed flags */
/* Yubikey 2.2 and above */
#define TKTFLAG_CHAL_RESP 0x40 /* Challenge-response enabled (both must be set) */
#define CFGFLAG_CHAL_YUBICO 0x20 /* Challenge-response enabled - Yubico OTP mode */
#define CFGFLAG_CHAL_HMAC 0x22 /* Challenge-response enabled - HMAC-SHA1 */
#define CFGFLAG_HMAC_LT64 0x04 /* Set when HMAC message is less than 64 bytes */
#define CFGFLAG_CHAL_BTN_TRIG 0x08 /* Challenge-response operation requires button press */
#define EXTFLAG_SERIAL_BTN_VISIBLE 0x01 /* Serial number visible at startup (button press) */
#define EXTFLAG_SERIAL_USB_VISIBLE 0x02 /* Serial number visible in USB iSerial field */
#define EXTFLAG_SERIAL_API_VISIBLE 0x04 /* Serial number visible via API call */
/* V2.3 flags only */
#define EXTFLAG_USE_NUMERIC_KEYPAD 0x08 /* Use numeric keypad for digits */
#define EXTFLAG_FAST_TRIG 0x10 /* Use fast trig if only cfg1 set */
#define EXTFLAG_ALLOW_UPDATE 0x20 /* Allow update of existing configuration (selected flags + access code) */
#define EXTFLAG_DORMANT 0x40 /* Dormant configuration (can be woken up and flag removed = requires update flag) */
/* Flags valid for update */
#define TKTFLAG_UPDATE_MASK (TKTFLAG_TAB_FIRST | TKTFLAG_APPEND_TAB1 | TKTFLAG_APPEND_TAB2 | TKTFLAG_APPEND_DELAY1 | TKTFLAG_APPEND_DELAY2 | TKTFLAG_APPEND_CR)
#define CFGFLAG_UPDATE_MASK 0
#define EXTFLAG_UPDATE_MASK (EXTFLAG_SERIAL_BTN_VISIBLE | EXTFLAG_SERIAL_USB_VISIBLE | EXTFLAG_SERIAL_API_VISIBLE | EXTFLAG_USE_NUMERIC_KEYPAD | EXTFLAG_FAST_TRIG | EXTFLAG_ALLOW_UPDATE)
/* Navigation */
/* NOTE: Navigation isn't available since Yubikey 1.3.5 and is strongly
discouraged. */
#define MAX_URL 48
struct nav_st {
unsigned char scancode[MAX_URL];/* Scancode (lower 7 bits) */
unsigned char scanmod[MAX_URL >> 2]; /* Modifier fields (packed 2 bits each) */
unsigned char flags; /* NAVFLAG_xxx flags */
unsigned char filler; /* Filler byte */
unsigned short crc; /* CRC16 value of all fields */
};
#define SCANMOD_SHIFT 0x80 /* Highest bit in scancode */
#define SCANMOD_ALT_GR 0x01 /* Lowest bit in mod */
#define SCANMOD_WIN 0x02 /* WIN key */
/* Navigation flags */
#define NAVFLAG_INSERT_TRIG 0x01 /* Automatic trigger when device is inserted */
#define NAVFLAG_APPEND_TKT 0x02 /* Append ticket to URL */
#define NAVFLAG_DUAL_KEY_USAGE 0x04 /* Dual usage of key: Short = ticket Long = Navigate */
/* Status block */
struct status_st {
unsigned char versionMajor; /* Firmware version information */
unsigned char versionMinor;
unsigned char versionBuild;
unsigned char pgmSeq; /* Programming sequence number. 0 if no valid configuration */
unsigned short touchLevel; /* Level from touch detector */
};
#define CONFIG1_VALID 0x01 /* Bit in touchLevel indicating that configuration 1 is valid (from firmware 2.1) */
#define CONFIG2_VALID 0x02 /* Bit in touchLevel indicating that configuration 2 is valid (from firmware 2.1) */
/* Modified hex string mapping */
#define MODHEX_MAP "cbdefghijklnrtuv"
#if defined(_WIN32) || defined(__GNUC__)
#pragma pack(pop)
#endif
#endif /* __YKDEF_H_INCLUDED__ */

View File

@ -1,37 +0,0 @@
#!/usr/bin/env python
"""
Set up a YubiKey NEO NDEF
"""
import sys
import struct
import six
import yubico
import yubico.yubikey_neo_usb_hid
if len(sys.argv) != 2:
sys.stderr.write("Syntax: %s URL\n" % (sys.argv[0]))
sys.exit(1)
url = sys.argv[1]
if sys.version_info >= (3, 0):
url = url.encode('utf-8')
try:
YK = yubico.yubikey_neo_usb_hid.YubiKeyNEO_USBHID(debug=True)
print("Version : %s " % YK.version())
ndef = yubico.yubikey_neo_usb_hid.YubiKeyNEO_NDEF(data = url)
user_input = six.moves.input('Write configuration to YubiKey? [y/N] : ')
if user_input in ('y', 'ye', 'yes'):
YK.write_ndef(ndef)
print("\nSuccess!")
else:
print("\nAborted")
except yubico.yubico_exception.YubicoError as inst:
print("ERROR: %s" % inst.reason)
sys.exit(1)

View File

@ -1,31 +0,0 @@
#!/usr/bin/env python
"""
Set up a YubiKey with a NIST PUB 198 A.2
20 bytes test vector in Slot 2 (variable input)
"""
import sys
import struct
import yubico
import six
slot=2
try:
YK = yubico.find_yubikey(debug=True)
print("Version : %s " % YK.version())
Cfg = YK.init_config()
key = b'h:303132333435363738393a3b3c3d3e3f40414243'
Cfg.mode_challenge_response(key, type='HMAC', variable=True)
Cfg.extended_flag('SERIAL_API_VISIBLE', True)
user_input = six.moves.input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot )
if user_input in ('y', 'ye', 'yes'):
YK.write_config(Cfg, slot=slot)
print("\nSuccess!")
else:
print("\nAborted")
except yubico.yubico_exception.YubicoError as inst:
print("ERROR: %s" % inst.reason)
sys.exit(1)

View File

@ -1,46 +0,0 @@
#!/usr/bin/env python
"""
Test challenge-response, assumes a NIST PUB 198 A.2
20 bytes test vector in Slot 2 (variable input)
"""
import sys
import yubico
expected = \
b'\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82' + \
b'\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24'
# turn on YubiKey debug if -v is given as an argument
debug = False
if len(sys.argv) > 1:
debug = (sys.argv[1] == '-v')
# Look for and initialize the YubiKey
try:
YK = yubico.find_yubikey(debug=debug)
print("Version : %s " % YK.version())
print("Serial : %i" % YK.serial())
print("")
# Do challenge-response
secret = b'Sample #2'.ljust(64, b'\0')
print("Sending challenge : %s\n" % repr(secret))
response = YK.challenge_response(secret, slot=2)
except yubico.yubico_exception.YubicoError as inst:
print("ERROR: %s" % inst.reason)
sys.exit(1)
print("Response :\n%s\n" % yubico.yubico_util.hexdump(response))
# Workaround for http://bugs.python.org/issue24596
del YK
# Check if the response matched the expected one
if response == expected:
print("OK! Response matches the NIST PUB 198 A.2 expected response.")
sys.exit(0)
else:
print("ERROR! Response does NOT match the NIST PUB 198 A.2 expected response.")
sys.exit(1)

View File

@ -1,247 +0,0 @@
#!/usr/bin/env python
#
# Copyright (c) 2011, Yubico AB
# See the file COPYING for licence statement.
#
"""
Demonstrate rolling challenges.
This is a scheme for generating "one time" HMAC-SHA1 challenges, which
works by being able to access the HMAC-SHA1 key on the host computer every
time the correct response is provided.
GPGME would've been used to encrypt the HMAC-SHA1 with the next expected
response, but none of the two Python bindings to gpgme I have available
at the moment supports symmetric encryption, so for demo purposes AES CBC
is used instead.
"""
import os
import sys
import json
import hmac
import argparse
import hashlib
import binascii
import yubico
import six
from Crypto.Cipher import AES
def parse_args():
"""
Parse the command line arguments
"""
parser = argparse.ArgumentParser(description = "Demonstrate rolling challenges",
add_help=True
)
parser.add_argument('-v', '--verbose',
dest='verbose',
action='store_true', default=False,
help='Enable verbose operation.'
)
parser.add_argument('--debug',
dest='debug',
action='store_true', default=False,
help='Enable debug operation.'
)
parser.add_argument('--init',
dest='init',
action='store_true', default=False,
help='Initialize demo.'
)
parser.add_argument('-F', '--filename',
dest='filename',
required=True,
help='State filename.'
)
parser.add_argument('--challenge-length',
dest='challenge_length',
type = int, default = 32,
help='Length of challenges generated, in bytes.'
)
parser.add_argument('--slot',
dest='slot',
type = int, default = 2,
help='YubiKey slot to send challenge to.'
)
args = parser.parse_args()
return args
def init_demo(args):
""" Initializes the demo by asking a few questions and creating a new stat file. """
hmac_key = six.moves.input("Enter HMAC-SHA1 key as 40 chars of hex (or press enter for random key) : ")
if hmac_key:
try:
hmac_key = binascii.unhexlify(hmac_key)
except:
sys.stderr.write("Could not decode HMAC-SHA1 key. Please enter 40 hex-chars.\n")
sys.exit(1)
else:
hmac_key = os.urandom(20)
if len(hmac_key) != 20:
sys.stderr.write("Decoded HMAC-SHA1 key is %i bytes, expected 20.\n" %( len(hmac_key)))
sys.exit(1)
print("To program a YubiKey >= 2.2 for challenge-response with this key, use :")
print("")
print(" $ ykpersonalize -%i -ochal-resp -ochal-hmac -ohmac-lt64 -a %s" % (args.slot, binascii.hexlify(hmac_key).decode('ascii')))
print("")
passphrase = six.moves.input("Enter the secret passphrase to protect with the rolling challenges : ")
secret_dict = {"count": 0,
"passphrase": passphrase,
}
roll_next_challenge(args, hmac_key, secret_dict)
def do_challenge(args):
""" Send a challenge to the YubiKey and use the result to decrypt the state file. """
outer_j = load_state_file(args)
challenge = outer_j["challenge"]
print("Challenge : %s" % (challenge))
response = get_yubikey_response(args, binascii.unhexlify(outer_j["challenge"]))
if args.debug or args.verbose:
print("\nGot %i bytes response %s\n" % (len(response), binascii.hexlify(response)))
else:
print("Response : %s" % binascii.hexlify(response))
inner_j = decrypt_with_response(args, outer_j["inner"], response)
if args.verbose or args.debug:
print("\nDecrypted 'inner' :\n%s\n" % (inner_j))
secret_dict = {}
try:
secret_dict = json.loads(inner_j.decode('ascii'))
except ValueError:
sys.stderr.write("\nCould not parse decoded data as JSON, you probably did not produce the right response.\n")
sys.exit(1)
secret_dict["count"] += 1
print("\nThe passphrase protected using rolling challenges is :\n")
print("\t%s\n\nAccessed %i times.\n" % (secret_dict["passphrase"], secret_dict["count"]))
roll_next_challenge(args, binascii.unhexlify(secret_dict["hmac_key"]), secret_dict)
def get_yubikey_response(args, challenge):
"""
Do challenge-response with the YubiKey if one is found. Otherwise prompt user to fake a response. """
try:
YK = yubico.find_yubikey(debug = args.debug)
response = YK.challenge_response(challenge.ljust(64, b'\0'), slot = args.slot)
return response
except yubico.yubico_exception.YubicoError as e:
print("YubiKey challenge-response failed (%s)" % e.reason)
print("")
response = six.moves.input("Assuming you do not have a YubiKey. Enter repsonse manually (hex encoded) : ")
return binascii.unhexlify(response)
def roll_next_challenge(args, hmac_key, inner_dict):
"""
When we have the HMAC-SHA1 key in clear, generate a random challenge and compute the
expected response for that challenge.
hmac_key is a 20-byte bytestring
"""
if len(hmac_key) != 20 or not isinstance(hmac_key, bytes):
hmac_key = binascii.unhexlify(hmac_key)
challenge = os.urandom(args.challenge_length)
response = get_response(hmac_key, challenge)
print("Generated challenge : %s" % binascii.hexlify(challenge).decode('ascii'))
print("Expected response : %s (sssh, don't tell anyone)" % binascii.hexlify(response).decode('ascii'))
print("")
if args.debug or args.verbose or args.init:
print("To manually verify that your YubiKey produces this response, use :")
print("")
print(" $ ykchalresp -%i -x %s" % (args.slot, binascii.hexlify(challenge).decode('ascii')))
print("")
inner_dict["hmac_key"] = binascii.hexlify(hmac_key).decode('ascii')
inner_j = json.dumps(inner_dict, indent = 4)
if args.verbose or args.debug:
print("Inner JSON :\n%s\n" % (inner_j))
inner_ciphertext = encrypt_with_response(args, inner_j, response)
outer_dict = {"challenge": binascii.hexlify(challenge).decode('ascii'),
"inner": inner_ciphertext.decode('ascii'),
}
outer_j = json.dumps(outer_dict, indent = 4)
if args.verbose or args.debug:
print("\nOuter JSON :\n%s\n" % (outer_j))
print("Saving 'outer' JSON to file '%s'" % (args.filename))
write_state_file(args, outer_j)
def get_response(hmac_key, challenge):
""" Compute the expected response for `challenge', as hexadecimal string """
print(binascii.hexlify(hmac_key), binascii.hexlify(challenge), hashlib.sha1)
h = hmac.new(hmac_key, challenge, hashlib.sha1)
return h.digest()
def encrypt_with_response(args, data, key):
"""
Encrypt our secret inner data with the response we expect the next time.
NOTE: The use of AES CBC has not been validated as cryptographically sound
in this application.
I would have done this with GPGme if it weren't for the fact that neither
of the two versions for Python available in Ubuntu 10.10 have support for
symmetric encrypt/decrypt (LP: #295918).
"""
# pad data to multiple of 16 bytes for AES CBC
pad = len(data) % 16
data += ' ' * (16 - pad)
# need to pad key as well
aes_key = key
aes_key += b'\0' * (32 - len(aes_key))
if args.debug:
print(("AES-CBC encrypting 'inner' with key (%i bytes) : %s" % (len(aes_key), binascii.hexlify(aes_key))))
obj = AES.new(aes_key, AES.MODE_CBC, b'\0' * 16)
ciphertext = obj.encrypt(data)
return binascii.hexlify(ciphertext)
def decrypt_with_response(args, data, key):
"""
Try to decrypt the secret inner data with the response we got to this challenge.
"""
aes_key = key
try:
aes_key = binascii.unhexlify(key)
except (TypeError, binascii.Error):
# was not hex encoded
pass
# need to pad key
aes_key += b'\0' * (32 - len(aes_key))
if args.debug:
print(("AES-CBC decrypting 'inner' using key (%i bytes) : %s" % (len(aes_key), binascii.hexlify(aes_key))))
obj = AES.new(aes_key, AES.MODE_CBC, b'\0' * 16)
plaintext = obj.decrypt(binascii.unhexlify(data))
return plaintext
def write_state_file(args, data):
""" Save state to file. """
f = open(args.filename, 'w')
f.write(data)
f.close()
def load_state_file(args):
""" Load (and parse) the state file. """
return json.loads(open(args.filename).read())
def main():
args = parse_args()
if args.init:
init_demo(args)
else:
do_challenge(args)
print("\nDone\n")
if __name__ == '__main__':
main()

View File

@ -1,46 +0,0 @@
#!/usr/bin/env python
"""
Set up a YubiKey for standard OTP with CR, then remove it.
"""
import sys
import struct
import yubico
import six
import binascii
slot=2
try:
YK = yubico.find_yubikey(debug=True)
print("Version : %s " % YK.version())
print("Status : %s " % YK.status())
Cfg = YK.init_config()
Cfg.extended_flag('ALLOW_UPDATE', True)
Cfg.ticket_flag('APPEND_CR', True)
Cfg.extended_flag('SERIAL_API_VISIBLE', True)
Cfg.uid = binascii.unhexlify('010203040506')
Cfg.fixed_string("m:ftccftbbftdd")
Cfg.aes_key('h:' + 32 * 'a')
user_input = six.moves.input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot )
if user_input in ('y', 'ye', 'yes'):
YK.write_config(Cfg, slot=slot)
print("\nSuccess!")
print("Status : %s " % YK.status())
else:
print("\nAborted")
sys.exit(0)
six.moves.input("Press enter to update...")
Cfg = YK.init_config(update=True)
Cfg.ticket_flag('APPEND_CR', False)
print ("Updating...");
YK.write_config(Cfg, slot=slot)
print("\nSuccess!")
except yubico.yubico_exception.YubicoError as inst:
print("ERROR: %s" % inst.reason)
sys.exit(1)

View File

@ -1,37 +0,0 @@
#!/usr/bin/env python
"""
Example of how to access more than one connected YubiKey.
"""
import sys
import yubico
def get_all_yubikeys(debug):
"""
Look for YubiKey with ever increasing `skip' value until an error is returned.
Return all instances of class YubiKey we got before failing.
"""
res = []
try:
skip = 0
while skip < 255:
YK = yubico.find_yubikey(debug = debug, skip = skip)
res.append(YK)
skip += 1
except yubico.yubikey.YubiKeyError:
pass
return res
debug = False
if len(sys.argv) > 1:
debug = (sys.argv[1] == '-v')
keys = get_all_yubikeys(debug)
if not keys:
print("No YubiKey found.")
else:
n = 1
for this in keys:
print("YubiKey #%02i : %s %s" % (n, this.description, this.status()))
n += 1

View File

@ -0,0 +1,12 @@
-----BEGIN PGP SIGNATURE-----
Comment: Use "gpg --dearmor" for unpacking
iQEzBAABCgAdFiEEIO4yW4aoG8vT5WeY8ENnCW+6legFAlx3xJEACgkQ8ENnCW+6
lejV/Qf9GHqWjV5eEx7zgko9RJupKW3148JStYA2X2WOplvMet9EkfDGMwYwTA9k
w6o2qwPYEvZl+/EbIFH9fWGR6h1sfJvmEwWbiL4MBKHT7gEHUvyr+xYEaQqQIuBe
ONH4cYb6eEZFHvj4YcWV6CM6ID5UOF0V0K46x4SrnovZhUBqM86vhmv199wgsk6C
LMeOkpcYR9LODPTwMucbCifsfwc967IGgOpqDHqiC0XGuhmDu8gbrkIGnh1zw9vM
zDpa/UCfZTV+N3ycsaYSK/cDNgtM9xjqGIRQ3BZu0d8J1JwhCVgwIshhgZA8G/Vn
yBA9l1dIPY1G3gwtyjFP0umMLWW9GA==
=RqWa
-----END PGP ARMORED FILE-----

Binary file not shown.

View File

@ -0,0 +1 @@
b347ad44153c85785dd2be83e0ccc7e57e2d2bf7

View File

@ -1,18 +0,0 @@
Metadata-Version: 1.2
Name: python-yubico
Version: 1.3.3
Summary: Python code for talking to Yubico's YubiKeys
Home-page: https://github.com/Yubico/python-yubico
Author: Dain Nilsson
Author-email: dain@yubico.com
Maintainer: Yubico Open Source Maintainers
Maintainer-email: ossmaint@yubico.com
License: BSD 2 clause
Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators

View File

@ -1,42 +0,0 @@
COPYING
ChangeLog
MANIFEST.in
NEWS
README
release.py
setup.cfg
setup.py
doc/ykdef.h
examples/configure_neo_ndef
examples/configure_nist_test_key
examples/nist_challenge_response
examples/rolling_challenge_response
examples/update_cfg_remove_cr
examples/yubikey-inventory
python_yubico.egg-info/PKG-INFO
python_yubico.egg-info/SOURCES.txt
python_yubico.egg-info/dependency_links.txt
python_yubico.egg-info/requires.txt
python_yubico.egg-info/top_level.txt
test/__init__.py
test/soft/__init__.py
test/soft/test_yubico.py
test/soft/test_yubikey_config.py
test/soft/test_yubikey_frame.py
test/usb/__init__.py
test/usb/test_yubikey_usb_hid.py
util/yubikey-totp
util/yubikey-totp.1
yubico/__init__.py
yubico/yubico_exception.py
yubico/yubico_util.py
yubico/yubico_version.py
yubico/yubikey.py
yubico/yubikey_4_usb_hid.py
yubico/yubikey_base.py
yubico/yubikey_config.py
yubico/yubikey_config_util.py
yubico/yubikey_defs.py
yubico/yubikey_frame.py
yubico/yubikey_neo_usb_hid.py
yubico/yubikey_usb_hid.py

View File

@ -1 +0,0 @@
pyusb

View File

@ -1 +0,0 @@
yubico

View File

@ -1,149 +0,0 @@
# Copyright (c) 2013 Yubico AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from distutils import log
from distutils.core import Command
from distutils.errors import DistutilsSetupError
import os
import re
from datetime import date
class release(Command):
description = "create and release a new version"
user_options = [
('keyid', None, "GPG key to sign with"),
('skip-tests', None, "skip running the tests"),
('pypi', None, "publish to pypi"),
]
boolean_options = ['skip-tests', 'pypi']
def initialize_options(self):
self.keyid = None
self.skip_tests = 0
self.pypi = 0
def finalize_options(self):
self.cwd = os.getcwd()
self.fullname = self.distribution.get_fullname()
self.name = self.distribution.get_name()
self.version = self.distribution.get_version()
def _verify_version(self):
with open('NEWS', 'r') as news_file:
line = news_file.readline()
now = date.today().strftime('%Y-%m-%d')
if not re.search(r'Version %s \(released %s\)' % (self.version, now),
line):
raise DistutilsSetupError("Incorrect date/version in NEWS!")
def _verify_tag(self):
if os.system('git tag | grep -q "^%s\$"' % self.fullname) == 0:
raise DistutilsSetupError(
"Tag '%s' already exists!" % self.fullname)
def _sign(self):
if os.path.isfile('dist/%s.tar.gz.asc' % self.fullname):
# Signature exists from upload, re-use it:
sign_opts = ['--output dist/%s.tar.gz.sig' % self.fullname,
'--dearmor dist/%s.tar.gz.asc' % self.fullname]
else:
# No signature, create it:
sign_opts = ['--detach-sign', 'dist/%s.tar.gz' % self.fullname]
if self.keyid:
sign_opts.insert(1, '--default-key ' + self.keyid)
self.execute(os.system, ('gpg ' + (' '.join(sign_opts)),))
if os.system('gpg --verify dist/%s.tar.gz.sig' % self.fullname) != 0:
raise DistutilsSetupError("Error verifying signature!")
def _tag(self):
tag_opts = ['-s', '-m ' + self.fullname, self.fullname]
if self.keyid:
tag_opts[0] = '-u ' + self.keyid
self.execute(os.system, ('git tag ' + (' '.join(tag_opts)),))
def _do_call_publish(self, cmd):
self._published = os.system(cmd) == 0
def _publish(self):
web_repo = os.getenv('YUBICO_GITHUB_REPO')
if web_repo and os.path.isdir(web_repo):
artifacts = [
'dist/%s.tar.gz' % self.fullname,
'dist/%s.tar.gz.sig' % self.fullname
]
cmd = '%s/publish %s %s %s' % (
web_repo, self.name, self.version, ' '.join(artifacts))
self.execute(self._do_call_publish, (cmd,))
if self._published:
self.announce("Release published! Don't forget to:", log.INFO)
self.announce("")
self.announce(" (cd %s && git push)" % web_repo, log.INFO)
self.announce("")
else:
self.warn("There was a problem publishing the release!")
else:
self.warn("YUBICO_GITHUB_REPO not set or invalid!")
self.warn("This release will not be published!")
def run(self):
if os.getcwd() != self.cwd:
raise DistutilsSetupError("Must be in package root!")
self._verify_version()
self._verify_tag()
self.execute(os.system, ('git2cl > ChangeLog',))
if not self.skip_tests:
self.run_command('check')
try:
self.run_command('test')
except SystemExit as e:
if e.code != 0:
raise DistutilsSetupError("There were test failures!")
self.run_command('sdist')
if self.pypi:
cmd_obj = self.distribution.get_command_obj('upload')
cmd_obj.sign = True
if self.keyid:
cmd_obj.identity = self.keyid
self.run_command('upload')
self._sign()
self._tag()
self._publish()
self.announce("Release complete! Don't forget to:", log.INFO)
self.announce("")
self.announce(" git push && git push --tags", log.INFO)
self.announce("")

View File

@ -1,4 +0,0 @@
[egg_info]
tag_build =
tag_date = 0

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python
from setuptools import setup
from release import release
import re
VERSION_PATTERN = re.compile(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$")
def get_version():
"""Return the current version as defined by yubico/yubico_version.py."""
with open('yubico/yubico_version.py', 'r') as f:
match = VERSION_PATTERN.search(f.read())
return match.group(1)
setup(
name='python-yubico',
description='Python code for talking to Yubico\'s YubiKeys',
version=get_version(),
author='Dain Nilsson', # Original author: Fredrik Thulin
author_email='dain@yubico.com',
maintainer='Yubico Open Source Maintainers',
maintainer_email='ossmaint@yubico.com',
url='https://github.com/Yubico/python-yubico',
license='BSD 2 clause',
packages=['yubico'],
install_requires=['pyusb'],
test_suite='test',
cmdclass={'release': release},
classifiers=[
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
]
)

View File

@ -1,2 +0,0 @@
# Copyright (c) 2010, 2011, 2012 Yubico AB
# See the file COPYING for licence statement.

View File

@ -1,7 +0,0 @@
# Copyright (c) 2010, 2011, 2012 Yubico AB
# See the file COPYING for licence statement.
"""
Unit tests testing logic of the library.
These do not require a physical YubiKey to run.
"""

View File

@ -1,49 +0,0 @@
#!/usr/bin/env python
#
# Simple test cases for a Python version of the yubikey_crc16() function in ykcrc.c.
#
import struct
import unittest
import yubico.yubico_util as yubico_util
from yubico.yubico_util import crc16
CRC_OK_RESIDUAL=0xf0b8
class TestCRC(unittest.TestCase):
def test_first(self):
""" Test CRC16 trivial case """
buffer = b'\x01\x02\x03\x04'
crc = crc16(buffer)
self.assertEqual(crc, 0xc66e)
return buffer,crc
def test_second(self):
""" Test CRC16 residual calculation """
buffer,crc = self.test_first()
# Append 1st complement for a "self-verifying" block -
# from example in Yubikey low level interface
crc_inv = 0xffff - crc
buffer += struct.pack('<H', crc_inv)
crc2 = crc16(buffer)
self.assertEqual(crc2, CRC_OK_RESIDUAL)
def test_hexdump(self):
""" Test hexdump function, normal use """
bytes = b'\x01\x02\x03\x04\x05\x06\x07\x08'
self.assertEqual(yubico_util.hexdump(bytes, length=4), \
'0000 01 02 03 04\n0004 05 06 07 08\n')
def test_hexdump2(self):
""" Test hexdump function, with colors """
bytes = b'\x01\x02\x03\x04\x05\x06\x07\x08'
self.assertEqual(yubico_util.hexdump(bytes, length=4, colorize=True), \
'0000 \x1b[0m01 02 03\x1b[0m 04\n0004 \x1b[0m05 06 07\x1b[0m 08\n')
def test_modhex_decode(self):
""" Test modhex decoding """
self.assertEqual(b"0123456789abcdef", yubico_util.modhex_decode(b"cbdefghijklnrtuv"))
if __name__ == '__main__':
unittest.main()

View File

@ -1,307 +0,0 @@
#!/usr/bin/env python
import unittest
import yubico
import yubico.yubikey_config
from yubico.yubikey_usb_hid import YubiKeyConfigUSBHID
import yubico.yubico_util
import yubico.yubico_exception
class YubiKeyTests(unittest.TestCase):
def setUp(self):
version = (2, 2, 0,)
capa = yubico.yubikey_usb_hid.YubiKeyUSBHIDCapabilities( \
model = 'YubiKey', version = version, default_answer = False)
self.Config = YubiKeyConfigUSBHID(ykver = version, capabilities = capa)
def test_static_ticket(self):
""" Test static ticket """
#fixed: m:
#uid: h:000000000000
#key: h:e2bee9a36568a00d026a02f85e61e6fb
#acc_code: h:000000000000
#ticket_flags: APPEND_CR
#config_flags: STATIC_TICKET
expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80',
b'\x00\xe2\xbe\xe9\xa3\x65\x68\x83',
b'\xa0\x0d\x02\x6a\x02\xf8\x5e\x84',
b'\x61\xe6\xfb\x00\x00\x00\x00\x85',
b'\x00\x00\x00\x00\x20\x20\x00\x86',
b'\x00\x5a\x93\x00\x00\x00\x00\x87',
b'\x00\x01\x95\x56\x00\x00\x00\x89'
]
Config = self.Config
Config.aes_key(b'h:e2bee9a36568a00d026a02f85e61e6fb')
Config.ticket_flag('APPEND_CR', True)
Config.config_flag('STATIC_TICKET', True)
data = Config.to_frame(slot=1).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_static_ticket_with_access_code(self):
""" Test static ticket with unlock code """
#fixed: m:
#uid: h:000000000000
#key: h:e2bee9a36568a00d026a02f85e61e6fb
#acc_code: h:010203040506
#ticket_flags: APPEND_CR
#config_flags: STATIC_TICKET
expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80',
b'\x00\xe2\xbe\xe9\xa3\x65\x68\x83',
b'\xa0\x0d\x02\x6a\x02\xf8\x5e\x84',
b'\x61\xe6\xfb\x01\x02\x03\x04\x85',
b'\x05\x06\x00\x00\x20\x20\x00\x86',
b'\x00\x0d\x39\x01\x02\x03\x04\x87',
b'\x05\x06\x00\x00\x00\x00\x00\x88',
b'\x00\x01\xc2\xfc\x00\x00\x00\x89',
]
Config = self.Config
Config.aes_key(b'h:e2bee9a36568a00d026a02f85e61e6fb')
Config.ticket_flag('APPEND_CR', True)
Config.config_flag('STATIC_TICKET', True)
Config.unlock_key(b'h:010203040506')
data = Config.to_frame(slot=1).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_fixed_and_oath_hotp(self):
""" Test OATH HOTP with a fixed prefix-string """
#fixed: m:ftftftft
#uid: h:000000000000
#key: h:523d7ce7e7b6ee853517a3e3cc1985c7
#acc_code: h:000000000000
#ticket_flags: APPEND_CR|OATH_HOTP
#config_flags: OATH_FIXED_MODHEX1|OATH_FIXED_MODHEX2|STATIC_TICKET
expected = [b'\x4d\x4d\x4d\x4d\x00\x00\x00\x80',
b'\x00\x52\x3d\x7c\xe7\xe7\xb6\x83',
b'\xee\x85\x35\x17\xa3\xe3\xcc\x84',
b'\x19\x85\xc7\x00\x00\x00\x00\x85',
b'\x00\x00\x04\x00\x60\x70\x00\x86',
b'\x00\x72\xad\xaa\xbb\xcc\xdd\x87',
b'\xee\xff\x00\x00\x00\x00\x00\x88',
b'\x00\x03\xfe\xc4\x00\x00\x00\x89',
]
Config = self.Config
Config.aes_key(b'h:523d7ce7e7b6ee853517a3e3cc1985c7')
Config.fixed_string(b'm:ftftftft')
Config.ticket_flag('APPEND_CR', True)
Config.ticket_flag('OATH_HOTP', True)
Config.config_flag('OATH_FIXED_MODHEX1', True)
Config.config_flag('OATH_FIXED_MODHEX2', True)
Config.config_flag('STATIC_TICKET', True)
Config.unlock_key(b'h:aabbccddeeff')
Config.access_key(b'h:000000000000')
data = Config.to_frame(slot=2).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_challenge_response_hmac_nist(self):
""" Test HMAC challenge response with NIST test vector """
expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80',
b'\x00\x00\x40\x41\x42\x43\x00\x82',
b'\x00\x30\x31\x32\x33\x34\x35\x83',
b'\x36\x37\x38\x39\x3a\x3b\x3c\x84',
b'\x3d\x3e\x3f\x00\x00\x00\x00\x85',
b'\x00\x00\x00\x04\x40\x26\x00\x86',
b'\x00\x98\x41\x00\x00\x00\x00\x87',
b'\x00\x03\x95\x56\x00\x00\x00\x89',
]
Config = self.Config
secret = b'h:303132333435363738393a3b3c3d3e3f40414243'
Config.mode_challenge_response(secret, type='HMAC', variable=True)
Config.extended_flag('SERIAL_API_VISIBLE', True)
data = Config.to_frame(slot=2).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_unknown_ticket_flag(self):
""" Test setting unknown ticket flag """
Config = self.Config
self.assertRaises(yubico.yubico_exception.InputError, Config.ticket_flag, 'YK_UNIT_TEST123', True)
def test_unknown_ticket_flag_integer(self):
""" Test setting unknown ticket flag as integer """
future_flag = 0xff
Config = self.Config
Config.ticket_flag(future_flag, True)
self.assertEqual(future_flag, Config.ticket_flags.to_integer())
def test_too_long_fixed_string(self):
""" Test too long fixed string, and set as plain string """
Config = self.Config
self.assertRaises(yubico.yubico_exception.InputError, Config.ticket_flag, 'YK_UNIT_TEST123', True)
def test_default_flags(self):
""" Test that no flags get set by default """
Config = self.Config
self.assertEqual(0x0, Config.ticket_flags.to_integer())
self.assertEqual(0x0, Config.config_flags.to_integer())
self.assertEqual(0x0, Config.extended_flags.to_integer())
def test_oath_hotp_like_windows(self):
""" Test plain OATH-HOTP with NIST test vector """
expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80',
b'\x00\x00\x40\x41\x42\x43\x00\x82',
b'\x00\x30\x31\x32\x33\x34\x35\x83',
b'\x36\x37\x38\x39\x3a\x3b\x3c\x84',
b'\x3d\x3e\x3f\x00\x00\x00\x00\x85',
b'\x00\x00\x00\x00\x40\x00\x00\x86',
b'\x00\x6a\xb9\x00\x00\x00\x00\x87',
b'\x00\x03\x95\x56\x00\x00\x00\x89',
]
Config = self.Config
secret = b'h:303132333435363738393a3b3c3d3e3f40414243'
Config.mode_oath_hotp(secret)
data = Config.to_frame(slot=2).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_oath_hotp_like_windows2(self):
""" Test OATH-HOTP with NIST test vector and token identifier """
expected = [b'\x01\x02\x03\x04\x05\x06\x00\x80',
b'\x00\x00\x40\x41\x42\x43\x00\x82',
b'\x00\x30\x31\x32\x33\x34\x35\x83',
b'\x36\x37\x38\x39\x3a\x3b\x3c\x84',
b'\x3d\x3e\x3f\x00\x00\x00\x00\x85',
b'\x00\x00\x06\x00\x40\x42\x00\x86',
b'\x00\x0e\xec\x00\x00\x00\x00\x87',
b'\x00\x03\x95\x56\x00\x00\x00\x89',
]
Config = self.Config
secret = b'h:303132333435363738393a3b3c3d3e3f40414243'
Config.mode_oath_hotp(secret, digits=8, factor_seed='', omp=0x01, tt=0x02, mui=b'\x03\x04\x05\x06')
Config.config_flag('OATH_FIXED_MODHEX2', True)
data = Config.to_frame(slot=2).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_oath_hotp_like_windows_factory_seed(self):
""" Test OATH-HOTP factor_seed """
expected = [b'\x01\x02\x03\x04\x05\x06\x00\x80',
b'\x00\x00\x40\x41\x42\x43\x01\x82',
b'\x21\x30\x31\x32\x33\x34\x35\x83',
b'\x36\x37\x38\x39\x3a\x3b\x3c\x84',
b'\x3d\x3e\x3f\x00\x00\x00\x00\x85',
b'\x00\x00\x06\x00\x40\x42\x00\x86',
b'\x00\x03\xea\x00\x00\x00\x00\x87',
b'\x00\x03\x95\x56\x00\x00\x00\x89',
]
Config = self.Config
secret = b'h:303132333435363738393a3b3c3d3e3f40414243'
Config.mode_oath_hotp(secret, digits=8, factor_seed=0x2101, omp=0x01, tt=0x02, mui=b'\x03\x04\x05\x06')
Config.config_flag('OATH_FIXED_MODHEX2', True)
data = Config.to_frame(slot=2).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_fixed_length_hmac_like_windows(self):
""" Test fixed length HMAC SHA1 """
expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80',
b'\x00\x00\x40\x41\x42\x43\x00\x82',
b'\x00\x30\x31\x32\x33\x34\x35\x83',
b'\x36\x37\x38\x39\x3a\x3b\x3c\x84',
b'\x3d\x3e\x3f\x00\x00\x00\x00\x85',
b'\x00\x00\x00\x00\x40\x22\x00\x86',
b'\x00\xe9\x0f\x00\x00\x00\x00\x87',
b'\x00\x03\x95\x56\x00\x00\x00\x89',
]
Config = self.Config
secret = b'h:303132333435363738393a3b3c3d3e3f40414243'
Config.mode_challenge_response(secret, type='HMAC', variable=False)
data = Config.to_frame(slot=2).to_feature_reports()
print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)),
yubico.yubico_util.hexdump(b''.join(data))))
self.assertEqual(data, expected)
def test_version_required_1(self):
""" Test YubiKey 1 with v2 option """
version = (1, 3, 0,)
capa = yubico.yubikey_usb_hid.YubiKeyUSBHIDCapabilities( \
model = 'YubiKey', version = version, default_answer = False)
Config = YubiKeyConfigUSBHID(ykver = version, capabilities = capa)
self.assertRaises(yubico.yubikey.YubiKeyVersionError, Config.config_flag, 'SHORT_TICKET', True)
def test_version_required_2(self):
""" Test YubiKey 2 with v2 option """
Config = self.Config
Config.config_flag('SHORT_TICKET', True)
self.assertEqual((2, 0), Config.version_required())
def test_version_required_3(self):
""" Test YubiKey 2 with v1 option """
Config = self.Config
self.assertRaises(yubico.yubikey.YubiKeyVersionError, Config.config_flag, 'TICKET_FIRST', True)
def test_version_required_4(self):
""" Test YubiKey 2.1 with v2.2 mode """
version = (2, 1, 0,)
capa = yubico.yubikey_usb_hid.YubiKeyUSBHIDCapabilities( \
model = 'YubiKey', version = version, default_answer = False)
Config = YubiKeyConfigUSBHID(ykver = version, capabilities = capa)
secret = b'h:303132333435363738393a3b3c3d3e3f40414243'
self.assertRaises(yubico.yubikey.YubiKeyVersionError, Config.mode_challenge_response, secret)
def test_version_required_5(self):
""" Test YubiKey 2.2 with v2.2 mode """
Config = self.Config
secret = b'h:303132333435363738393a3b3c3d3e3f'
Config.mode_challenge_response(secret, type='OTP')
self.assertEqual('CHAL_RESP', Config._mode)
if __name__ == '__main__':
unittest.main()

View File

@ -1,58 +0,0 @@
#!/usr/bin/env python
from yubico import *
from yubico.yubikey_frame import *
import yubico.yubico_exception
import unittest
import struct
import re
class YubiKeyTests(unittest.TestCase):
def test_get_ykframe(self):
""" Test normal use """
buffer = YubiKeyFrame(command=0x01).to_string()
# check number of bytes returned
self.assertEqual(len(buffer), 70, "yubikey command buffer should always be 70 bytes")
# check that empty payload works (64 * '\x00')
all_zeros = b'\x00' * 64
self.assertTrue(buffer.startswith(all_zeros))
def test_get_ykframe_feature_reports(self):
""" Test normal use """
res = YubiKeyFrame(command=0x32).to_feature_reports()
self.assertEqual(res, [b'\x00\x00\x00\x00\x00\x00\x00\x80',
b'\x00\x32\x6b\x5b\x00\x00\x00\x89'
])
def test_get_ykframe_feature_reports2(self):
""" Test one serie of non-zero bytes in the middle of the payload """
payload = b'\x00' * 38
payload += b'\x01\x02\x03'
payload += b'\x00' * 23
res = YubiKeyFrame(command=0x32, payload=payload).to_feature_reports()
self.assertEqual(res, [b'\x00\x00\x00\x00\x00\x00\x00\x80',
b'\x00\x00\x00\x01\x02\x03\x00\x85',
b'\x002\x01s\x00\x00\x00\x89'])
def test_bad_payload(self):
""" Test that we get an exception for four bytes payload """
self.assertRaises(yubico_exception.InputError, YubiKeyFrame, command=0x32, payload=b'test')
def test_repr(self):
""" Test string representation of object """
# to achieve 100% test coverage ;)
frame = YubiKeyFrame(command=0x4d)
print("Frame is represented as %s" % frame)
re_match = re.search("YubiKeyFrame instance at .*: 77.$", str(frame))
self.assertNotEqual(re_match, None)
if __name__ == '__main__':
unittest.main()

View File

@ -1,7 +0,0 @@
# Copyright (c) 2010, 2011, 2012 Yubico AB
# See the file COPYING for licence statement.
"""
Tests that run against a physical YubiKey.
These tests require an attached YubiKey that has been correctly configured.
"""

View File

@ -1,56 +0,0 @@
#!/usr/bin/env python
#
# Test cases for talking to a USB HID YubiKey.
#
import struct
import unittest
import yubico
import yubico.yubikey_usb_hid
from yubico.yubikey_usb_hid import *
import re
class TestYubiKeyUSBHID(unittest.TestCase):
YK = None
def setUp(self):
""" Test connecting to the YubiKey """
if self.YK is None:
try:
print("open key")
self.YK = YubiKeyUSBHID()
return
except YubiKeyUSBHIDError as err:
self.fail("No YubiKey connected (?) : %s" % str(err))
def tearDown(self):
if self.YK is not None:
del self.YK
#@unittest.skipIf(YK is None, "No USB HID YubiKey found")
def test_status(self):
""" Test the simplest form of communication : a status read request """
status = self.YK.status()
version = self.YK.version()
print("Version returned: %s" % version)
re_match = re.match("\d+\.\d+\.\d+$", version)
self.assertNotEqual(re_match, None)
#@unittest.skipIf(self.YK is None, "No USB HID YubiKey found")
def test_challenge_response(self):
""" Test challenge-response, assumes a NIST PUB 198 A.2 20 bytes test vector in Slot 2 (variable input) """
secret = struct.pack('64s', b'Sample #2')
response = self.YK.challenge_response(secret, mode='HMAC', slot=2)
self.assertEqual(response, b'\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24')
#@unittest.skipIf(self.YK is None, "No USB HID YubiKey found")
def test_serial(self):
""" Test serial number retrieval (requires YubiKey 2) """
serial = self.YK.serial()
print("Serial returned : %s" % serial)
self.assertEqual(type(serial), type(1))
if __name__ == '__main__':
unittest.main()

View File

@ -1,132 +0,0 @@
#!/usr/bin/env python
#
# Copyright (c) 2011, Yubico AB
# See the file COPYING for licence statement.
#
"""
This program lets you use the HMAC-SHA-1 in your YubiKey to produce
OATH TOTP (RFC 6238) codes.
To verify the output of this program, first program a YubiKey with the
RFC 6238 test key "12345678901234567890" (ASCII) :
$ ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 \
-o serial-api-visible \
-a 3132333435363738393031323334353637383930
and then examine the OATH codes for the test values (Time) in Appendix B
of RFC 6238 (SHA1) :
Time SHA1
59 -> 94287082
1111111109 -> 07081804
1234567890 -> 89005924
20000000000 -> 65353130
Like this :
$ yubikey-totp --step 30 --digits 8 --time 59
94287082
$
"""
import sys
import time
import struct
import yubico
import argparse
import binascii
default_slot=2
default_time=int(time.time())
default_step=30
default_digits=6
def parse_args():
"""
Parse the command line arguments
"""
parser = argparse.ArgumentParser(description = "Generate OATH TOTP codes using a YubiKey",
add_help = True,
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument('-v', '--verbose',
dest='verbose',
action='store_true', default=False,
help='Enable verbose operation'
)
parser.add_argument('--debug',
dest='debug',
action='store_true', default=False,
help='Enable debug operation'
)
parser.add_argument('--time',
dest='time',
type=int, default=default_time,
required=False,
help='Time to use as number of seconds since epoch',
)
parser.add_argument('--step',
dest='step',
type=int, default=default_step,
required=False,
help='Time step in use (in seconds)',
)
parser.add_argument('--digits',
dest='digits',
type=int, default=default_digits,
required=False,
help='Length of OTP in decimal digits',
)
parser.add_argument('--slot',
dest='slot',
type=int, default=default_slot,
required=False,
help='YubiKey slot configured for Challenge-Response',
)
args = parser.parse_args()
return args
def make_totp(args):
"""
Create an OATH TOTP OTP and return it as a string (to disambiguate leading zeros).
"""
YK = yubico.find_yubikey(debug=args.debug)
if args.debug or args.verbose:
print("Version : %s " % YK.version())
if args.debug:
print("Serial : %i" % YK.serial())
print("")
# Do challenge-response
secret = struct.pack("> Q", args.time / args.step).ljust(64, chr(0x0))
if args.debug:
print("Sending challenge : %s\n" % (binascii.hexlify(secret)))
response = YK.challenge_response(secret, slot=args.slot)
# format with appropriate number of leading zeros
totp_str = '%.*i' % (args.digits, yubico.yubico_util.hotp_truncate(response, length=args.digits))
return totp_str
def main():
""" Main program. """
args = parse_args()
otp = None
try:
otp = make_totp(args)
except yubico.yubico_exception.YubicoError as e:
print("ERROR: %s" % (e.reason))
return 1
if not otp:
return 1
print(otp)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,102 +0,0 @@
.\" Copyright (c) 2012 Yubico AB
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions are
.\" met:
.\"
.\" * Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\"
.\" * Redistributions in binary form must reproduce the above
.\" copyright notice, this list of conditions and the following
.\" disclaimer in the documentation and/or other materials provided
.\" with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
.\" OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" The following commands are required for all man pages.
.de URL
\\$2 \(laURL: \\$1 \(ra\\$3
..
.if \n[.g] .mso www.tmac
.TH yubikey-totp "1" "June 2012" "python-yubico"
.SH NAME
yubikey-totp - Produce an OATH TOTP code using a YubiKey
.SH SYNOPSIS
.B yubikey-totp
[\fI-v\fR] [\fI-h\fR] [\fI--time\fR | \fI--step\fR] [\fI--digits\fR] [\fI--slot\fR] [\fI--debug\fR]
.SH DESCRIPTION
OATH codes are one time passwords (OTP) calculated in a standardized way. While the YubiKey
is primarily used with Yubico OTP's, the YubiKey is also capable of producing OATH codes.
OATH generally comes in two flavors -- event based (called HOTP) and time based (called TOTP).
Since the YubiKey does not contain a battery, it cannot keep track of the current time itself
and therefor a helper application such as yubikey-totp is required to effectively send the
current time to the YubiKey, which can then perform the cryptographic calculation needed to
produce the OATH code.
Through the use of a helper application, such as yubikey-totp, the YubiKey can be used with
sites offering OATH TOTP authentication, such as Google GMail.
.SH OPTIONS
.TP
\fB\-v\fR
enable verbose mode.
.TP
\fB\-h\fR
show help
.TP
\fB\-\-time\fR
specify the time value to use (in seconds since epoch)
.TP
\fB\-\-step\fR
how frequent codes change in your system - typically 30 or 60 seconds
.TP
\fB\-\-digits\fR
digits in OATH code - typically 6
.TP
\fB\-\-slot\fR
YubiKey slot to use - default 2
.TP
\fB\-\-debug\fR
enable debug output
.SH EXAMPLE
The YubiKey OATH TOTP operation can be demonstrated using the
\fBRFC 6238\fR test key "12345678901234567890" (ASCII).
.P
First, program a YubiKey for HMAC-SHA1 Challenge-Response operation with the test vector HMAC key :
.HP
.nf
$ \fBykpersonalize \-2 \-ochal\-resp \-ochal\-hmac \-ohmac\-lt64 \-o serial\-api\-visible \\
\-a 3132333435363738393031323334353637383930\fR
.fi
.HP
Now, send the NIST test challenge to the YubiKey and verify the result matches the
expected :
.HP
.nf
$ \fByubikey\-totp \-\-step 30 \-\-digits 8 \-\-time 1111111109\fR
07081804
$
.fi
.SH BUGS
Report yubikey-totp bugs in
.URL "https://github.com/Yubico/python-yubico/issues/" "the issue tracker" "."
.SH "SEE ALSO"
.PP
YubiKeys can be obtained from
.URL "http://www.yubico.com/" "Yubico" "."

View File

@ -1,44 +0,0 @@
"""
the yubico package
See http://www.yubico.com/yubikey/ for information about the YubiKey.
Example usage :
import yubico
try:
YK = yubico.find_yubikey(debug=True)
print "Version : %s " % YK.version()
except yubico.yubico_exception.YubicoError as e:
print "ERROR: %s" % e.reason
sys.exit(1)
To learn about configuring your YubiKey using this framework, see the
yubikey_config module.
"""
# Copyright (c) 2010, 2011, 2012 Yubico AB
# See the file COPYING for licence statement.
from .yubico_version import __version__
__all__ = [
# classes
'YubiKey',
# functions
"find_yubikey",
# modules
"yubico_exception",
"yubico_util",
"yubikey",
"yubikey_config",
"yubikey_config_util",
"yubikey_defs",
"yubikey_frame",
"yubikey_usb_hid",
"yubikey_neo_usb_hid",
]
# to not have to import yubico.yubikey
from .yubikey import YubiKey
from .yubikey import find_key as find_yubikey

View File

@ -1,54 +0,0 @@
"""
class for exceptions used in the other Yubico modules
All exceptions raised by the different Yubico modules are inherited
from the base class YubicoError. That means you can trap them all,
without knowing the details, with code like this :
try:
# something Yubico related
except yubico.yubico_exception.YubicoError as inst:
print "ERROR: %s" % inst.reason
"""
# Copyright (c) 2010, Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
# functions
# classes
'YubicoError',
'InputError',
'YubiKeyTimeout',
]
from .yubico_version import __version__
class YubicoError(Exception):
"""
Base class for Yubico exceptions in the yubico package.
Attributes:
reason -- explanation of the error
"""
def __init__(self, reason):
self.reason = reason
def __str__(self):
return '<%s instance at %s: %s>' % (
self.__class__.__name__,
hex(id(self)),
self.reason
)
pass
class InputError(YubicoError):
"""
Exception raised for errors in an input to some function.
"""
def __init__(self, reason='input validation error'):
super(InputError, self).__init__(reason)

View File

@ -1,158 +0,0 @@
"""
utility functions for Yubico modules
"""
# Copyright (c) 2010, Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
# functions
'crc16',
'validate_crc16',
'hexdump',
'modhex_decode',
'hotp_truncate',
# classes
]
import sys
import string
from .yubico_version import __version__
from . import yubikey_defs
from . import yubico_exception
_CRC_OK_RESIDUAL = 0xf0b8
def ord_byte(byte):
"""Convert a byte to its integer value"""
if sys.version_info < (3, 0):
return ord(byte)
else:
# In Python 3, single bytes are represented as integers
return int(byte)
def chr_byte(number):
"""Convert an integer value to a length-1 bytestring"""
if sys.version_info < (3, 0):
return chr(number)
else:
return bytes([number])
def crc16(data):
"""
Calculate an ISO13239 CRC checksum of the input buffer (bytestring).
"""
m_crc = 0xffff
for this in data:
m_crc ^= ord_byte(this)
for _ in range(8):
j = m_crc & 1
m_crc >>= 1
if j:
m_crc ^= 0x8408
return m_crc
def validate_crc16(data):
"""
Validate that the CRC of the contents of buffer is the residual OK value.
The input is a bytestring.
"""
return crc16(data) == _CRC_OK_RESIDUAL
class DumpColors:
""" Class holding ANSI colors for colorization of hexdump output """
def __init__(self):
self.colors = {'BLUE': '\033[94m',
'GREEN': '\033[92m',
'RESET': '\033[0m',
}
self.enabled = True
return None
def get(self, what):
"""
Get the ANSI code for 'what'
Returns an empty string if disabled/not found
"""
if self.enabled:
if what in self.colors:
return self.colors[what]
return ''
def enable(self):
""" Enable colorization """
self.enabled = True
def disable(self):
""" Disable colorization """
self.enabled = False
def hexdump(src, length=8, colorize=False):
""" Produce a string hexdump of src, for debug output.
Input: bytestring; output: text string
"""
if not src:
return str(src)
if type(src) is not bytes:
raise yubico_exception.InputError('Hexdump \'src\' must be bytestring (got %s)' % type(src))
offset = 0
result = ''
for this in group(src, length):
if colorize:
last, this = this[-1], this[:-1]
colors = DumpColors()
color = colors.get('RESET')
if ord_byte(last) & yubikey_defs.RESP_PENDING_FLAG:
# write to key
color = colors.get('BLUE')
elif ord_byte(last) & yubikey_defs.SLOT_WRITE_FLAG:
color = colors.get('GREEN')
hex_s = color + ' '.join(["%02x" % ord_byte(x) for x in this]) + colors.get('RESET')
hex_s += " %02x" % ord_byte(last)
else:
hex_s = ' '.join(["%02x" % ord_byte(x) for x in this])
result += "%04X %s\n" % (offset, hex_s)
offset += length
return result
def group(data, num):
""" Split data into chunks of num chars each """
return [data[i:i+num] for i in range(0, len(data), num)]
def modhex_decode(data):
""" Convert a modhex bytestring to ordinary hex. """
try:
maketrans = string.maketrans
except AttributeError:
# Python 3
maketrans = bytes.maketrans
t_map = maketrans(b"cbdefghijklnrtuv", b"0123456789abcdef")
return data.translate(t_map)
def hotp_truncate(hmac_result, length=6):
""" Perform the HOTP Algorithm truncating.
Input is a bytestring.
"""
if len(hmac_result) != 20:
raise yubico_exception.YubicoError("HMAC-SHA-1 not 20 bytes long")
offset = ord_byte(hmac_result[19]) & 0xf
bin_code = (ord_byte(hmac_result[offset]) & 0x7f) << 24 \
| (ord_byte(hmac_result[offset+1]) & 0xff) << 16 \
| (ord_byte(hmac_result[offset+2]) & 0xff) << 8 \
| (ord_byte(hmac_result[offset+3]) & 0xff)
return bin_code % (10 ** length)
def tlv_parse(data):
""" Parses a bytestring of TLV values into a dict with the tags as keys."""
parsed = {}
while data:
t, l, data = ord_byte(data[0]), ord_byte(data[1]), data[2:]
parsed[t], data = data[:l], data[l:]
return parsed

View File

@ -1 +0,0 @@
__version__ = "1.3.3"

View File

@ -1,67 +0,0 @@
"""
module for accessing a YubiKey
In an attempt to support any future versions of the YubiKey which
might not be USB HID devices, you should always use the yubikey.find_key()
(or better yet, yubico.find_yubikey()) function to initialize
communication with YubiKeys.
Example usage (if using this module directly, see base module yubico) :
import yubico.yubikey
try:
YK = yubico.yubikey.find_key()
print "Version : %s " % YK.version()
except yubico.yubico_exception.YubicoError as inst:
print "ERROR: %s" % inst.reason
"""
# Copyright (c) 2010, 2011, 2012 Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
'RESP_TIMEOUT_WAIT_FLAG',
'RESP_PENDING_FLAG',
'SLOT_WRITE_FLAG',
# functions
'find_key',
# classes
'YubiKey',
'YubiKeyTimeout',
]
from .yubico_version import __version__
from .yubikey_base import YubiKeyError, YubiKeyTimeout, YubiKeyVersionError, YubiKeyCapabilities, YubiKey
from .yubikey_usb_hid import YubiKeyUSBHID, YubiKeyHIDDevice, YubiKeyUSBHIDError
from .yubikey_neo_usb_hid import YubiKeyNEO_USBHID
from .yubikey_4_usb_hid import YubiKey4_USBHID
def find_key(debug=False, skip=0):
"""
Locate a connected YubiKey. Throws an exception if none is found.
This function is supposed to be possible to extend if any other YubiKeys
appear in the future.
Attributes :
skip -- number of YubiKeys to skip
debug -- True or False
"""
try:
hid_device = YubiKeyHIDDevice(debug, skip)
yk_version = hid_device.status().ykver()
if (2, 1, 4) <= yk_version <= (2, 1, 9):
return YubiKeyNEO_USBHID(debug, skip, hid_device)
if yk_version < (3, 0, 0):
return YubiKeyUSBHID(debug, skip, hid_device)
if yk_version < (4, 0, 0):
return YubiKeyNEO_USBHID(debug, skip, hid_device)
return YubiKey4_USBHID(debug, skip, hid_device)
except YubiKeyUSBHIDError as inst:
if 'No USB YubiKey found' in str(inst):
# generalize this error
raise YubiKeyError('No YubiKey found')
else:
raise

View File

@ -1,113 +0,0 @@
"""
module for accessing a USB HID YubiKey 4
"""
# Copyright (c) 2012 Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
# functions
# classes
'YubiKey4_USBHID',
'YubiKey4_USBHIDError'
]
from .yubikey_defs import SLOT, MODE, YK4_CAPA
from . import yubikey_frame
from . import yubikey_base
from . import yubico_exception
from . import yubico_util
from . import yubikey_neo_usb_hid
MODE_CAPABILITIES = { # Required capabilities to support USB mode.
MODE.OTP : [YK4_CAPA.OTP],
MODE.CCID : [YK4_CAPA.CCID],
MODE.OTP_CCID : [YK4_CAPA.OTP, YK4_CAPA.CCID],
MODE.U2F : [YK4_CAPA.U2F],
MODE.OTP_U2F : [YK4_CAPA.OTP, YK4_CAPA.U2F],
MODE.U2F_CCID : [YK4_CAPA.U2F, YK4_CAPA.CCID],
MODE.OTP_U2F_CCID : [YK4_CAPA.OTP, YK4_CAPA.U2F, YK4_CAPA.CCID]
}
class YubiKey4_USBHIDError(yubico_exception.YubicoError):
""" Exception raised for errors with the YK4 USB HID communication. """
class YubiKey4_USBHIDCapabilities(yubikey_neo_usb_hid.YubiKeyNEO_USBHIDCapabilities):
"""
Capabilities of current YubiKey 4.
"""
_yk4_capa = 0
def _set_yk4_capa(self, yk4_capa):
int_val = 0
for b in yk4_capa:
int_val <<= 8
int_val += yubico_util.ord_byte(b)
self._yk4_capa = int_val
def have_nfc_ndef(self, slot=1):
return False
def have_usb_mode(self, mode):
mode &= ~MODE.FLAG_EJECT # Mask away eject flag
if self.version < (4, 1, 0): # YK Plus is locked in OTP+U2F
return mode == MODE.OTP_U2F
for cap_req in MODE_CAPABILITIES.get(mode, [0]):
if not self.have_capability(cap_req):
return False
return True
def have_capabilities(self):
return self.version >= (4, 1, 0)
def have_capability(self, capability):
return self._yk4_capa & capability != 0
class YubiKey4_USBHID(yubikey_neo_usb_hid.YubiKeyNEO_USBHID):
"""
Class for accessing a YubiKey 4 over USB HID.
"""
model = 'YubiKey 4'
description = 'YubiKey 4'
_capabilities_cls = YubiKey4_USBHIDCapabilities
def __init__(self, debug=False, skip=0, hid_device=None):
"""
Find and connect to a YubiKey 4 (USB HID).
Attributes :
skip -- number of YubiKeys to skip
debug -- True or False
"""
super(YubiKey4_USBHID, self).__init__(debug, skip, hid_device)
if self.version_num() < (4, 0, 0):
raise yubikey_base.YubiKeyVersionError(
"Incorrect version for YubiKey 4 %s" % self.version())
elif self.version_num() < (4, 1, 0):
self.description = 'YubiKey Plus'
elif self.version_num() < (4, 2, 0):
self.description = 'YubiKey Edge/Edge-n'
if self.capabilities.have_capabilities():
data = yubico_util.tlv_parse(self._read_capabilities())
self.capabilities._set_yk4_capa(data.get(YK4_CAPA.TAG.CAPA, b''))
def _read_capabilities(self):
""" Read the capabilities list from a YubiKey >= 4.0.0 """
frame = yubikey_frame.YubiKeyFrame(command=SLOT.YK4_CAPABILITIES)
self._device._write(frame)
response = self._device._read_response()
r_len = yubico_util.ord_byte(response[0])
# 1 byte length, 2 byte CRC.
if not yubico_util.validate_crc16(response[:r_len+3]):
raise YubiKey4_USBHIDError("Read from device failed CRC check")
return response[1:r_len+1]

View File

@ -1,198 +0,0 @@
"""
module for Yubikey base classes
"""
# Copyright (c) 2010, 2011, 2012 Yubico AB
# See the file COPYING for licence statement.
from .yubico_version import __version__
from . import yubico_exception
class YubiKeyError(yubico_exception.YubicoError):
"""
Exception raised concerning YubiKey operations.
Attributes:
reason -- explanation of the error
"""
def __init__(self, reason='no details'):
super(YubiKeyError, self).__init__(reason)
class YubiKeyTimeout(YubiKeyError):
"""
Exception raised when a YubiKey operation timed out.
Attributes:
reason -- explanation of the error
"""
def __init__(self, reason='no details'):
super(YubiKeyTimeout, self).__init__(reason)
class YubiKeyVersionError(YubiKeyError):
"""
Exception raised when the YubiKey is not capable of something requested.
Attributes:
reason -- explanation of the error
"""
def __init__(self, reason='no details'):
super(YubiKeyVersionError, self).__init__(reason)
class YubiKeyCapabilities(object):
"""
Class expressing the functionality of a YubiKey.
This base class should be the superset of all sub-classes.
In this base class, we lie and say 'yes' to all capabilities.
If the base class is used (such as when creating a YubiKeyConfig()
before getting a YubiKey()), errors must be handled at runtime
(or later, when the user is unable to use the YubiKey).
"""
model = 'Unknown'
version = (0, 0, 0,)
version_num = 0x0
default_answer = True
def __init__(self, model = None, version = None, default_answer = None):
self.model = model
if default_answer is not None:
self.default_answer = default_answer
if version is not None:
self.version = version
(major, minor, build,) = version
# convert 2.1.3 to 0x00020103
self.version_num = (major << 24) | (minor << 16) | build
return None
def __repr__(self):
return '<%s instance at %s: Device %s %s (default: %s)>' % (
self.__class__.__name__,
hex(id(self)),
self.model,
self.version,
self.default_answer,
)
def have_yubico_OTP(self):
return self.default_answer
def have_OATH(self, mode):
return self.default_answer
def have_challenge_response(self, mode):
return self.default_answer
def have_serial_number(self):
return self.default_answer
def have_ticket_flag(self, flag):
return self.default_answer
def have_config_flag(self, flag):
return self.default_answer
def have_extended_flag(self, flag):
return self.default_answer
def have_extended_scan_code_mode(self):
return self.default_answer
def have_shifted_1_mode(self):
return self.default_answer
def have_nfc_ndef(self, slot=1):
return self.default_answer
def have_configuration_slot(self):
return self.default_answer
def have_device_config(self):
return self.default_answer
def have_usb_mode(self, mode):
return self.default_answer
def have_scanmap(self):
return self.default_answer
def have_capabilities(self):
return self.default_answer
def have_capability(self, capability):
return self.default_answer
class YubiKey(object):
"""
Base class for accessing YubiKeys
"""
debug = None
capabilities = None
def __init__(self, debug, capabilities = None):
self.debug = debug
if capabilities is None:
self.capabilities = YubiKeyCapabilities(default_answer = False)
else:
self.capabilities = capabilities
return None
def version(self):
""" Get the connected YubiKey's version as a string. """
pass
def serial(self, may_block=True):
"""
Get the connected YubiKey's serial number.
Note that since version 2.?.? this requires the YubiKey to be
configured with the extended flag SERIAL_API_VISIBLE.
If the YubiKey is configured with SERIAL_BTN_VISIBLE set to True,
it will start blinking and require a button press before revealing
the serial number, with a 15 seconds timeout. Set `may_block'
to False to abort if this is the case.
"""
pass
def challenge(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True):
"""
Get the response to a challenge from a connected YubiKey.
`mode' is either 'HMAC' or 'OTP'.
`slot' is 1 or 2.
`variable' is only relevant for mode == HMAC.
If variable is True, challenge will be padded such that the
YubiKey will compute the HMAC as if there were no padding.
If variable is False, challenge will always be NULL-padded
to 64 bytes.
The special case of no input will be HMACed by the YubiKey
(in variable HMAC mode) as data = 0x00, length = 1.
In mode 'OTP', the challenge should be exactly 6 bytes. The
response will be a YubiKey "ticket" with the 6-byte challenge
in the ticket.uid field. The rest of the "ticket" will contain
timestamp and counter information, so two identical challenges
will NOT result in the same responses. The response is
decryptable using AES ECB if you have access to the AES key
programmed into the YubiKey.
"""
pass
def init_config(self):
"""
Return a YubiKey configuration object for this type of YubiKey.
"""
pass
def write_config(self, cfg, slot):
"""
Configure a YubiKey using a configuration object.
"""
pass

View File

@ -1,557 +0,0 @@
"""
module for configuring YubiKeys
"""
# Copyright (c) 2010, 2012 Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
'TicketFlags',
'ConfigFlags',
'ExtendedFlags',
# functions
# classes
'YubiKeyConfigError',
'YubiKeyConfig',
]
from .yubico_version import __version__
import sys
import struct
import binascii
from . import yubico_util
from . import yubikey_defs
from . import yubikey_frame
from . import yubico_exception
from . import yubikey_base
from .yubikey_config_util import YubiKeyConfigBits, YubiKeyConfigFlag, YubiKeyExtendedFlag, YubiKeyTicketFlag
from .yubikey_defs import SLOT
def command2str(num):
""" Turn command number into name """
for attr in SLOT.__dict__.keys():
if not attr.startswith('_') and attr == attr.upper():
if getattr(SLOT, attr) == num:
return 'SLOT_%s' % attr
return "0x%02x" % (num)
### BEGIN DEPRECATED
### These are here for backwards compatibility, DO NOT USE!
SLOT_CONFIG = SLOT.CONFIG
SLOT_CONFIG2 = SLOT.CONFIG2
SLOT_UPDATE1 = SLOT.UPDATE1
SLOT_UPDATE2 = SLOT.UPDATE2
SLOT_SWAP = SLOT.SWAP
### END DEPRECATED
TicketFlags = [
YubiKeyTicketFlag('TAB_FIRST', 0x01, min_ykver=(1, 0), doc='Send TAB before first part'),
YubiKeyTicketFlag('APPEND_TAB1', 0x02, min_ykver=(1, 0), doc='Send TAB after first part'),
YubiKeyTicketFlag('APPEND_TAB2', 0x04, min_ykver=(1, 0), doc='Send TAB after second part'),
YubiKeyTicketFlag('APPEND_DELAY1', 0x08, min_ykver=(1, 0), doc='Add 0.5s delay after first part'),
YubiKeyTicketFlag('APPEND_DELAY2', 0x10, min_ykver=(1, 0), doc='Add 0.5s delay after second part'),
YubiKeyTicketFlag('APPEND_CR', 0x20, min_ykver=(1, 0), doc='Append CR as final character'),
YubiKeyTicketFlag('OATH_HOTP', 0x40, min_ykver=(2, 1), doc='Choose OATH-HOTP mode'),
YubiKeyTicketFlag('CHAL_RESP', 0x40, min_ykver=(2, 2), doc='Choose Challenge-Response mode'),
YubiKeyTicketFlag('PROTECT_CFG2', 0x80, min_ykver=(2, 0), doc='Protect configuration in slot 2'),
]
ConfigFlags = [
YubiKeyConfigFlag('SEND_REF', 0x01, min_ykver=(1, 0), doc='Send reference string (0..F) before data'),
YubiKeyConfigFlag('TICKET_FIRST', 0x02, min_ykver=(1, 0), doc='Send ticket first (default is fixed part)', max_ykver=(1, 9)),
YubiKeyConfigFlag('PACING_10MS', 0x04, min_ykver=(1, 0), doc='Add 10ms intra-key pacing'),
YubiKeyConfigFlag('PACING_20MS', 0x08, min_ykver=(1, 0), doc='Add 20ms intra-key pacing'),
#YubiKeyConfigFlag('ALLOW_HIDTRIG', 0x10, min_ykver=(1, 0), doc='DONT USE: Allow trigger through HID/keyboard', max_ykver=(1, 9)),
YubiKeyConfigFlag('STATIC_TICKET', 0x20, min_ykver=(1, 0), doc='Static ticket generation'),
# YubiKey 2.0 and above
YubiKeyConfigFlag('SHORT_TICKET', 0x02, min_ykver=(2, 0), doc='Send truncated ticket (half length)'),
YubiKeyConfigFlag('STRONG_PW1', 0x10, min_ykver=(2, 0), doc='Strong password policy flag #1 (mixed case)'),
YubiKeyConfigFlag('STRONG_PW2', 0x40, min_ykver=(2, 0), doc='Strong password policy flag #2 (subtitute 0..7 to digits)'),
YubiKeyConfigFlag('MAN_UPDATE', 0x80, min_ykver=(2, 0), doc='Allow manual (local) update of static OTP'),
# YubiKey 2.1 and above
YubiKeyConfigFlag('OATH_HOTP8', 0x02, min_ykver=(2, 1), mode='OATH', doc='Generate 8 digits HOTP rather than 6 digits'),
YubiKeyConfigFlag('OATH_FIXED_MODHEX1', 0x10, min_ykver=(2, 1), mode='OATH', doc='First byte in fixed part sent as modhex'),
YubiKeyConfigFlag('OATH_FIXED_MODHEX2', 0x40, min_ykver=(2, 1), mode='OATH', doc='First two bytes in fixed part sent as modhex'),
YubiKeyConfigFlag('OATH_FIXED_MODHEX', 0x50, min_ykver=(2, 1), mode='OATH', doc='Fixed part sent as modhex'),
YubiKeyConfigFlag('OATH_FIXED_MASK', 0x50, min_ykver=(2, 1), mode='OATH', doc='Mask to get out fixed flags'),
# YubiKey 2.2 and above
YubiKeyConfigFlag('CHAL_YUBICO', 0x20, min_ykver=(2, 2), mode='CHAL', doc='Challenge-response enabled - Yubico OTP mode'),
YubiKeyConfigFlag('CHAL_HMAC', 0x22, min_ykver=(2, 2), mode='CHAL', doc='Challenge-response enabled - HMAC-SHA1'),
YubiKeyConfigFlag('HMAC_LT64', 0x04, min_ykver=(2, 2), mode='CHAL', doc='Set when HMAC message is less than 64 bytes'),
YubiKeyConfigFlag('CHAL_BTN_TRIG', 0x08, min_ykver=(2, 2), mode='CHAL', doc='Challenge-respoonse operation requires button press'),
]
ExtendedFlags = [
YubiKeyExtendedFlag('SERIAL_BTN_VISIBLE', 0x01, min_ykver=(2, 2), doc='Serial number visible at startup (button press)'),
YubiKeyExtendedFlag('SERIAL_USB_VISIBLE', 0x02, min_ykver=(2, 2), doc='Serial number visible in USB iSerial field'),
YubiKeyExtendedFlag('SERIAL_API_VISIBLE', 0x04, min_ykver=(2, 2), doc='Serial number visible via API call'),
# YubiKey 2.3 and above
YubiKeyExtendedFlag('USE_NUMERIC_KEYPAD', 0x08, min_ykver=(2, 3), doc='Use numeric keypad for digits'),
YubiKeyExtendedFlag('FAST_TRIG', 0x10, min_ykver=(2, 3), doc='Use fast trig if only cfg1 set'),
YubiKeyExtendedFlag('ALLOW_UPDATE', 0x20, min_ykver=(2, 3), doc='Allow update of existing configuration (selected flags + access code)'),
YubiKeyExtendedFlag('DORMANT', 0x40, min_ykver=(2, 3), doc='Dormant configuration (can be woken up and flag removed = requires update flag)'),
]
class YubiKeyConfigError(yubico_exception.YubicoError):
"""
Exception raised for YubiKey configuration errors.
"""
class YubiKeyConfig(object):
"""
Base class for configuration of all current types of YubiKeys.
"""
def __init__(self, ykver=None, capabilities=None, update=False, swap=False,
zap=False):
"""
`ykver' is a tuple (major, minor) with the version number of the key
you are planning to apply this configuration to. Not mandated, but
will get you an exception when trying to set flags for example, rather
than the YubiKey just not operating as expected after programming.
YubiKey >= 2.3 supports updating certain parts of a configuration
(for example turning on/off APPEND_CR) without overwriting others
(most notably the stored secret). Set `update' to True if this is
what you want. The current programming must have flag 'ALLOW_UPDATE'
set to allow configuration update instead of requiring complete
reprogramming.
YubiKey >= 2.3 also supports swapping the configurations, making
slot 1 be slot 2 and vice versa. Set swap=True for this.
YubiKeys support deleting a configuration, setting it in an
unprogrammed state. Set zap=True for this.
"""
if capabilities is None:
self.capabilities = yubikey_base.YubiKeyCapabilities(default_answer = True)
else:
self.capabilities = capabilities
# Minimum version of YubiKey this configuration will require
self.yk_req_version = (0, 0)
self.ykver = ykver
self.fixed = b''
self.uid = b''
self.key = b''
self.access_code = b''
self.ticket_flags = YubiKeyConfigBits(0x0)
self.config_flags = YubiKeyConfigBits(0x0)
self.extended_flags = YubiKeyConfigBits(0x0)
self.unlock_code = b''
self._mode = ''
if update or swap:
self._require_version(major=2, minor=3)
self._update_config = update
self._swap_slots = swap
self._zap = zap
return None
def __repr__(self):
return '<%s instance at %s: mode %s, v=%s/%s, lf=%i, lu=%i, lk=%i, lac=%i, tf=%x, cf=%x, ef=%x, lu=%i, up=%s, sw=%s, z=%s>' % (
self.__class__.__name__,
hex(id(self)),
self._mode,
self.yk_req_version, self.ykver,
len(self.fixed),
len(self.uid),
len(self.key),
len(self.access_code),
self.ticket_flags.to_integer(),
self.config_flags.to_integer(),
self.extended_flags.to_integer(),
len(self.unlock_code),
self._update_config,
self._swap_slots,
self._zap
)
def version_required(self):
"""
Return the (major, minor) versions of YubiKey required for this configuration.
"""
return self.yk_req_version
def fixed_string(self, data=None):
"""
The fixed string is used to identify a particular Yubikey device.
The fixed string is referred to as the 'Token Identifier' in OATH-HOTP mode.
The length of the fixed string can be set between 0 and 16 bytes.
Tip: This can also be used to extend the length of a static password.
"""
old = self.fixed
if data != None:
new = self._decode_input_string(data)
if len(new) <= 16:
self.fixed = new
else:
raise yubico_exception.InputError('The "fixed" string must be 0..16 bytes')
return old
def enable_extended_scan_code_mode(self):
"""
Extended scan code mode means the Yubikey will output the bytes in
the 'fixed string' as scan codes, without modhex encoding the data.
Because of the way this is stored in the config flags, it is not
possible to disable this option once it is enabled (of course, you
can abort config update or reprogram the YubiKey again).
Requires YubiKey 2.x.
"""
if not self.capabilities.have_extended_scan_code_mode():
raise
self._require_version(major=2)
self.config_flag('SHORT_TICKET', True)
self.config_flag('STATIC_TICKET', False)
def enable_shifted_1(self):
"""
This will cause a shifted character 1 (typically '!') to be sent before
anything else. This can be used to make the YubiKey output qualify as a
password with 'special characters', if such is required.
Because of the way this is stored in the config flags, it is not
possible to disable this option once it is enabled (of course, you
can abort config update or reprogram the YubiKey again).
Requires YubiKey 2.x.
"""
self._require_version(major=2)
self.config_flag('STRONG_PW2', True)
self.config_flag('SEND_REF', True)
def aes_key(self, data):
"""
AES128 key to program into YubiKey.
Supply data as either a raw string, or a hexlified string prefixed by 'h:'.
The result, after any hex decoding, must be 16 bytes.
"""
old = self.key
if data:
new = self._decode_input_string(data)
if len(new) == 16:
self.key = new
else:
raise yubico_exception.InputError('AES128 key must be exactly 16 bytes')
return old
def unlock_key(self, data):
"""
Access code to allow re-programming of your YubiKey.
Supply data as either a raw bytestring, or a hexlified bytestring prefixed by 'h:'.
The result, after any hex decoding, must be 6 bytes.
"""
if data.startswith(b'h:'):
new = binascii.unhexlify(data[2:])
else:
new = data
if len(new) == 6:
self.unlock_code = new
if not self.access_code:
# Don't reset the access code when programming, unless that seems
# to be the intent of the calling program.
self.access_code = new
else:
raise yubico_exception.InputError('Unlock key must be exactly 6 bytes')
def access_key(self, data):
"""
Set a new access code which will be required for future re-programmings of your YubiKey.
Supply data as either a raw string, or a hexlified string prefixed by 'h:'.
The result, after any hex decoding, must be 6 bytes.
"""
if data.startswith(b'h:'):
new = binascii.unhexlify(data[2:])
else:
new = data
if len(new) == 6:
self.access_code = new
else:
raise yubico_exception.InputError('Access key must be exactly 6 bytes')
def mode_yubikey_otp(self, private_uid, aes_key):
"""
Set the YubiKey up for standard OTP validation.
"""
if not self.capabilities.have_yubico_OTP():
raise yubikey_base.YubiKeyVersionError('Yubico OTP not available in %s version %d.%d' \
% (self.capabilities.model, self.ykver[0], self.ykver[1]))
if private_uid.startswith(b'h:'):
private_uid = binascii.unhexlify(private_uid[2:])
if len(private_uid) != yubikey_defs.UID_SIZE:
raise yubico_exception.InputError('Private UID must be %i bytes' % (yubikey_defs.UID_SIZE))
self._change_mode('YUBIKEY_OTP', major=0, minor=9)
self.uid = private_uid
self.aes_key(aes_key)
def mode_oath_hotp(self, secret, digits=6, factor_seed=None, omp=0x0, tt=0x0, mui=''):
"""
Set the YubiKey up for OATH-HOTP operation.
Requires YubiKey 2.1.
"""
if not self.capabilities.have_OATH('HOTP'):
raise yubikey_base.YubiKeyVersionError('OATH HOTP not available in %s version %d.%d' \
% (self.capabilities.model, self.ykver[0], self.ykver[1]))
if digits != 6 and digits != 8:
raise yubico_exception.InputError('OATH-HOTP digits must be 6 or 8')
self._change_mode('OATH_HOTP', major=2, minor=1)
self._set_20_bytes_key(secret)
if digits == 8:
self.config_flag('OATH_HOTP8', True)
if omp or tt or mui:
decoded_mui = self._decode_input_string(mui)
fixed = yubico_util.chr_byte(omp) + yubico_util.chr_byte(tt) + decoded_mui
self.fixed_string(fixed)
if factor_seed:
self.uid = self.uid + struct.pack('<H', factor_seed)
def mode_challenge_response(self, secret, type='HMAC', variable=True, require_button=False):
"""
Set the YubiKey up for challenge-response operation.
`type' can be 'HMAC' or 'OTP'.
`variable' is only applicable to type 'HMAC'.
For type HMAC, `secret' is expected to be 20 bytes (160 bits).
For type OTP, `secret' is expected to be 16 bytes (128 bits).
Requires YubiKey 2.2.
"""
if not type.upper() in ['HMAC', 'OTP']:
raise yubico_exception.InputError('Invalid \'type\' (%s)' % type)
if not self.capabilities.have_challenge_response(type.upper()):
raise yubikey_base.YubiKeyVersionError('%s Challenge-Response not available in %s version %d.%d' \
% (type.upper(), self.capabilities.model, \
self.ykver[0], self.ykver[1]))
self._change_mode('CHAL_RESP', major=2, minor=2)
if type.upper() == 'HMAC':
self.config_flag('CHAL_HMAC', True)
self.config_flag('HMAC_LT64', variable)
self._set_20_bytes_key(secret)
else:
# type is 'OTP', checked above
self.config_flag('CHAL_YUBICO', True)
self.aes_key(secret)
self.config_flag('CHAL_BTN_TRIG', require_button)
def ticket_flag(self, which, new=None):
"""
Get or set a ticket flag.
'which' can be either a string ('APPEND_CR' etc.), or an integer.
You should ALWAYS use a string, unless you really know what you are doing.
"""
flag = _get_flag(which, TicketFlags)
if flag:
if not self.capabilities.have_ticket_flag(flag):
raise yubikey_base.YubiKeyVersionError('Ticket flag %s requires %s, and this is %s %d.%d'
% (which, flag.req_string(self.capabilities.model), \
self.capabilities.model, self.ykver[0], self.ykver[1]))
req_major, req_minor = flag.req_version()
self._require_version(major=req_major, minor=req_minor)
value = flag.to_integer()
else:
if type(which) is not int:
raise yubico_exception.InputError('Unknown non-integer TicketFlag (%s)' % which)
value = which
return self.ticket_flags.get_set(value, new)
def config_flag(self, which, new=None):
"""
Get or set a config flag.
'which' can be either a string ('PACING_20MS' etc.), or an integer.
You should ALWAYS use a string, unless you really know what you are doing.
"""
flag = _get_flag(which, ConfigFlags)
if flag:
if not self.capabilities.have_config_flag(flag):
raise yubikey_base.YubiKeyVersionError('Config flag %s requires %s, and this is %s %d.%d'
% (which, flag.req_string(self.capabilities.model), \
self.capabilities.model, self.ykver[0], self.ykver[1]))
req_major, req_minor = flag.req_version()
self._require_version(major=req_major, minor=req_minor)
value = flag.to_integer()
else:
if type(which) is not int:
raise yubico_exception.InputError('Unknown non-integer ConfigFlag (%s)' % which)
value = which
return self.config_flags.get_set(value, new)
def extended_flag(self, which, new=None):
"""
Get or set a extended flag.
'which' can be either a string ('SERIAL_API_VISIBLE' etc.), or an integer.
You should ALWAYS use a string, unless you really know what you are doing.
"""
flag = _get_flag(which, ExtendedFlags)
if flag:
if not self.capabilities.have_extended_flag(flag):
raise yubikey_base.YubiKeyVersionError('Extended flag %s requires %s, and this is %s %d.%d'
% (which, flag.req_string(self.capabilities.model), \
self.capabilities.model, self.ykver[0], self.ykver[1]))
req_major, req_minor = flag.req_version()
self._require_version(major=req_major, minor=req_minor)
value = flag.to_integer()
else:
if type(which) is not int:
raise yubico_exception.InputError('Unknown non-integer ExtendedFlag (%s)' % which)
value = which
return self.extended_flags.get_set(value, new)
def to_string(self):
"""
Return the current configuration as a bytestring (always 64 bytes).
"""
#define UID_SIZE 6 /* Size of secret ID field */
#define FIXED_SIZE 16 /* Max size of fixed field */
#define KEY_SIZE 16 /* Size of AES key */
#define KEY_SIZE_OATH 20 /* Size of OATH-HOTP key (key field + first 4 of UID field) */
#define ACC_CODE_SIZE 6 /* Size of access code to re-program device */
#
#struct config_st {
# unsigned char fixed[FIXED_SIZE];/* Fixed data in binary format */
# unsigned char uid[UID_SIZE]; /* Fixed UID part of ticket */
# unsigned char key[KEY_SIZE]; /* AES key */
# unsigned char accCode[ACC_CODE_SIZE]; /* Access code to re-program device */
# unsigned char fixedSize; /* Number of bytes in fixed field (0 if not used) */
# unsigned char extFlags; /* Extended flags */
# unsigned char tktFlags; /* Ticket configuration flags */
# unsigned char cfgFlags; /* General configuration flags */
# unsigned char rfu[2]; /* Reserved for future use */
# unsigned short crc; /* CRC16 value of all fields */
#};
t_rfu = 0
first = struct.pack('<16s6s16s6sBBBBH',
self.fixed,
self.uid,
self.key,
self.access_code,
len(self.fixed),
self.extended_flags.to_integer(),
self.ticket_flags.to_integer(),
self.config_flags.to_integer(),
t_rfu
)
crc = 0xffff - yubico_util.crc16(first)
second = first + struct.pack('<H', crc) + self.unlock_code
return second
def to_frame(self, slot=1):
"""
Return the current configuration as a YubiKeyFrame object.
"""
data = self.to_string()
payload = data.ljust(64, yubico_util.chr_byte(0x0))
if slot is 1:
if self._update_config:
command = SLOT.UPDATE1
else:
command = SLOT.CONFIG
elif slot is 2:
if self._update_config:
command = SLOT.UPDATE2
else:
command = SLOT.CONFIG2
else:
assert()
if self._swap_slots:
command = SLOT.SWAP
if self._zap:
payload = b''
return yubikey_frame.YubiKeyFrame(command=command, payload=payload)
def _require_version(self, major, minor=0):
""" Update the minimum version of YubiKey this configuration can be applied to. """
new_ver = (major, minor)
if self.ykver and new_ver > self.ykver:
raise yubikey_base.YubiKeyVersionError('Configuration requires YubiKey %d.%d, and this is %d.%d'
% (major, minor, self.ykver[0], self.ykver[1]))
if new_ver > self.yk_req_version:
self.yk_req_version = new_ver
def _decode_input_string(self, data):
if sys.version_info >= (3, 0) and isinstance(data, str):
data = data.encode('ascii')
if data.startswith(b'm:'):
data = b'h:' + yubico_util.modhex_decode(data[2:])
if data.startswith(b'h:'):
return(binascii.unhexlify(data[2:]))
else:
return(data)
def _change_mode(self, mode, major, minor):
""" Change mode of operation, with some sanity checks. """
if self._mode:
if self._mode != mode:
raise RuntimeError('Can\'t change mode (from %s to %s)' % (self._mode, mode))
self._require_version(major=major, minor=minor)
self._mode = mode
# when setting mode, we reset all flags
self.ticket_flags = YubiKeyConfigBits(0x0)
self.config_flags = YubiKeyConfigBits(0x0)
self.extended_flags = YubiKeyConfigBits(0x0)
if mode != 'YUBIKEY_OTP':
self.ticket_flag(mode, True)
def _set_20_bytes_key(self, data):
"""
Set a 20 bytes key. This is used in CHAL_HMAC and OATH_HOTP mode.
Supply data as either a raw bytestring, or a hexlified bytestring prefixed by 'h:'.
The result, after any hex decoding, must be 20 bytes.
"""
if data.startswith(b'h:'):
new = binascii.unhexlify(data[2:])
else:
new = data
if len(new) == 20:
self.key = new[:16]
self.uid = new[16:]
else:
raise yubico_exception.InputError('HMAC key must be exactly 20 bytes')
def _get_flag(which, flags):
""" Find 'which' entry in 'flags'. """
res = [this for this in flags if this.is_equal(which)]
if len(res) == 0:
return None
if len(res) == 1:
return res[0]
assert()

View File

@ -1,171 +0,0 @@
"""
utility functions used in yubikey_config.
"""
# Copyright (c) 2010, 2012 Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
# functions
# classes
'YubiKeyConfigBits',
'YubiKeyConfigFlag',
'YubiKeyExtendedFlag',
'YubiKeyTicketFlag',
]
class YubiKeyFlag(object):
"""
A flag value, and associated metadata.
"""
def __init__(self, key, value, doc=None, min_ykver=(0, 0), max_ykver=None, models=['YubiKey', 'YubiKey NEO', 'YubiKey 4']):
"""
Metadata about a ticket/config/extended flag bit.
@param key: Name of flag, such as 'APPEND_CR'
@param value: Bit value, 0x20 for APPEND_CR
@param doc: Human readable description of flag
@param min_ykver: Tuple with the minimum version required (major, minor,)
@param min_ykver: Tuple with the maximum version required (major, minor,) (for depreacted flags)
@param models: List of model identifiers (strings) that support this flag
"""
if type(key) is not str:
assert()
if type(value) is not int:
assert()
if type(min_ykver) is not tuple:
assert()
if type(models) is not list:
assert()
self.key = key
self.value = value
self.doc = doc
self.min_ykver = min_ykver
self.max_ykver = max_ykver
self.models = models
return None
def __repr__(self):
return '<%s instance at %s: %s (0x%x)>' % (
self.__class__.__name__,
hex(id(self)),
self.key,
self.value
)
def is_equal(self, key):
""" Check if key is equal to that of this instance """
return self.key == key
def to_integer(self):
""" Return flag value """
return self.value
def req_version(self):
""" Return the minimum required version """
return self.min_ykver
def req_string(self, model):
""" Return string describing model and version requirement. """
if model not in self.models:
model = self.models
if self.min_ykver and self.max_ykver:
return "%s %d.%d..%d.%d" % (model, \
self.min_ykver[0], self.min_ykver[1], \
self.max_ykver[0], self.max_ykver[1], \
)
if self.max_ykver:
return "%s <= %d.%d" % (model, self.max_ykver[0], self.max_ykver[1])
return "%s >= %d.%d" % (model, self.min_ykver[0], self.min_ykver[1])
def is_compatible(self, model, version):
""" Check if this flag is compatible with a YubiKey of version 'ver'. """
if not model in self.models:
return False
if self.max_ykver:
return (version >= self.min_ykver and
version <= self.max_ykver)
else:
return version >= self.min_ykver
class YubiKeyTicketFlag(YubiKeyFlag):
"""
A ticket flag value, and associated metadata.
"""
class YubiKeyConfigFlag(YubiKeyFlag):
"""
A config flag value, and associated metadata.
"""
def __init__(self, key, value, mode='', doc=None, min_ykver=(0, 0), max_ykver=None):
if type(mode) is not str:
assert()
self.mode = mode
super(YubiKeyConfigFlag, self).__init__(key, value, doc=doc, min_ykver=min_ykver, max_ykver=max_ykver)
class YubiKeyExtendedFlag(YubiKeyFlag):
"""
An extended flag value, and associated metadata.
"""
def __init__(self, key, value, mode='', doc=None, min_ykver=(2, 2), max_ykver=None):
if type(mode) is not str:
assert()
self.mode = mode
super(YubiKeyExtendedFlag, self).__init__(key, value, doc=doc, min_ykver=min_ykver, max_ykver=max_ykver)
class YubiKeyConfigBits(object):
"""
Class to hold bit values for configuration.
"""
def __init__(self, default=0x0):
self.value = default
return None
def __repr__(self):
return '<%s instance at %s: value 0x%x>' % (
self.__class__.__name__,
hex(id(self)),
self.value,
)
def get_set(self, flag, new):
"""
Return the boolean value of 'flag'. If 'new' is set,
the flag is updated, and the value before update is
returned.
"""
old = self._is_set(flag)
if new is True:
self._set(flag)
elif new is False:
self._clear(flag)
return old
def to_integer(self):
""" Return the sum of all flags as an integer. """
return self.value
def _is_set(self, flag):
""" Check if flag is set. Returns True or False. """
return self.value & flag == flag
def _set(self, flag):
""" Set flag. """
self.value |= flag
def _clear(self, flag):
""" Clear flag. """
self.value &= (0xff - flag)

View File

@ -1,211 +0,0 @@
"""
Module with constants. Many of them from ykdefs.h.
"""
# Copyright (c) 2010, 2011 Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
'RESP_TIMEOUT_WAIT_MASK',
'RESP_TIMEOUT_WAIT_FLAG',
'RESP_PENDING_FLAG',
'SLOT_WRITE_FLAG',
'SHA1_MAX_BLOCK_SIZE',
'SHA1_DIGEST_SIZE',
'OTP_CHALRESP_SIZE',
'UID_SIZE',
'YUBICO_VID',
# functions
# classes
'SLOT',
'MODE',
'PID',
'YK4_CAPA'
]
from .yubico_version import __version__
# Yubikey Low level interface #2.3
RESP_TIMEOUT_WAIT_MASK = 0x1f # Mask to get timeout value
RESP_TIMEOUT_WAIT_FLAG = 0x20 # Waiting for timeout operation - seconds left in lower 5 bits
RESP_PENDING_FLAG = 0x40 # Response pending flag
SLOT_WRITE_FLAG = 0x80 # Write flag - set by app - cleared by device
SHA1_MAX_BLOCK_SIZE = 64 # Max size of input SHA1 block
SHA1_DIGEST_SIZE = 20 # Size of SHA1 digest = 160 bits
OTP_CHALRESP_SIZE = 16 # Number of bytes returned for an Yubico-OTP challenge (not from ykdef.h)
UID_SIZE = 6 # Size of secret ID field
class SLOT(object):
"""Slot entries"""
CONFIG = 0x01 # First (default / V1) configuration
CONFIG2 = 0x03 # Second (V2) configuration
UPDATE1 = 0x04 # Update slot 1
UPDATE2 = 0x05 # Update slot 2
SWAP = 0x06 # Swap slot 1 and 2
NDEF = 0x08 # Write NDEF record
NDEF2 = 0x09 # Write NDEF record for slot 2
DEVICE_SERIAL = 0x10 # Device serial number
DEVICE_CONFIG = 0x11 # Write device configuration record
SCAN_MAP = 0x12 # Write scancode map
YK4_CAPABILITIES = 0x13 # Read YK4 capabilities list
CHAL_OTP1 = 0x20 # Write 6 byte challenge to slot 1, get Yubico OTP response
CHAL_OTP2 = 0x28 # Write 6 byte challenge to slot 2, get Yubico OTP response
CHAL_HMAC1 = 0x30 # Write 64 byte challenge to slot 1, get HMAC-SHA1 response
CHAL_HMAC2 = 0x38 # Write 64 byte challenge to slot 2, get HMAC-SHA1 response
class MODE(object):
"""USB modes"""
OTP = 0x00 # OTP only
CCID = 0x01 # CCID only, no eject
OTP_CCID = 0x02 # OTP + CCID composite
U2F = 0x03 # U2F mode
OTP_U2F = 0x04 # OTP + U2F composite
U2F_CCID = 0x05 # U2F + U2F composite
OTP_U2F_CCID = 0x06 # OTP + U2F + CCID composite
MASK = 0x07 # Mask for mode bits
FLAG_EJECT = 0x80 # CCID device supports eject (CCID) / OTP force eject (CCID composite)
@classmethod
def all(cls, otp=False, ccid=False, u2f=False):
"""Returns a set of all USB modes, with optional filtering"""
modes = set([
cls.OTP,
cls.CCID,
cls.OTP_CCID,
cls.U2F,
cls.OTP_U2F,
cls.U2F_CCID,
cls.OTP_U2F_CCID
])
if otp:
modes.difference_update(set([
cls.CCID,
cls.U2F,
cls.U2F_CCID
]))
if ccid:
modes.difference_update(set([
cls.OTP,
cls.U2F,
cls.OTP_U2F
]))
if u2f:
modes.difference_update(set([
cls.OTP,
cls.CCID,
cls.OTP_CCID
]))
return modes
YUBICO_VID = 0x1050 # Global vendor ID
class PID(object):
"""USB Product IDs"""
YUBIKEY = 0x0010 # Yubikey (version 1 and 2)
NEO_OTP = 0x0110 # Yubikey NEO - OTP only
NEO_OTP_CCID = 0x0111 # Yubikey NEO - OTP and CCID
NEO_CCID = 0x0112 # Yubikey NEO - CCID only
NEO_U2F = 0x0113 # Yubikey NEO - U2F only
NEO_OTP_U2F = 0x0114 # Yubikey NEO - OTP and U2F
NEO_U2F_CCID = 0x0115 # Yubikey NEO - U2F and CCID
NEO_OTP_U2F_CCID = 0x0116 # Yubikey NEO - OTP, U2F and CCID
NEO_SKY = 0x0120 # Security Key by Yubico
YK4_OTP = 0x0401 # Yubikey 4 - OTP only
YK4_U2F = 0x0402 # Yubikey 4 - U2F only
YK4_OTP_U2F = 0x0403 # Yubikey 4 - OTP and U2F
YK4_CCID = 0x0404 # Yubikey 4 - CCID only
YK4_OTP_CCID = 0x0405 # Yubikey 4 - OTP and CCID
YK4_U2F_CCID = 0x0406 # Yubikey 4 - U2F and CCID
YK4_OTP_U2F_CCID = 0x0407 # Yubikey 4 - OTP, U2F and CCID
PLUS_U2F_OTP = 0x0410 # Yubikey plus - OTP+U2F
@classmethod
def all(cls, otp=False, ccid=False, u2f=False):
"""Returns a set of all PIDs, with optional filtering"""
pids = set([
cls.YUBIKEY,
cls.NEO_OTP,
cls.NEO_OTP_CCID,
cls.NEO_CCID,
cls.NEO_U2F,
cls.NEO_OTP_U2F,
cls.NEO_U2F_CCID,
cls.NEO_OTP_U2F_CCID,
cls.NEO_SKY,
cls.YK4_OTP,
cls.YK4_U2F,
cls.YK4_OTP_U2F,
cls.YK4_CCID,
cls.YK4_OTP_CCID,
cls.YK4_U2F_CCID,
cls.YK4_OTP_U2F_CCID,
cls.PLUS_U2F_OTP
])
if otp:
pids.difference_update(set([
cls.NEO_CCID,
cls.NEO_U2F,
cls.NEO_U2F_CCID,
cls.NEO_SKY,
cls.YK4_U2F,
cls.YK4_CCID,
cls.YK4_U2F_CCID
]))
if ccid:
pids.difference_update(set([
cls.YUBIKEY,
cls.NEO_OTP,
cls.NEO_U2F,
cls.NEO_OTP_U2F,
cls.NEO_SKY,
cls.YK4_OTP,
cls.YK4_U2F,
cls.YK4_OTP_U2F,
cls.PLUS_U2F_OTP
]))
if u2f:
pids.difference_update(set([
cls.YUBIKEY,
cls.NEO_OTP,
cls.NEO_OTP_CCID,
cls.NEO_CCID,
cls.YK4_OTP,
cls.YK4_CCID,
cls.YK4_OTP_CCID
]))
return pids
class YK4_CAPA(object):
"""Capability bits in the YK4_CAPA field"""
OTP = 0x01 # OTP functionality
U2F = 0x02 # U2F functionality
CCID = 0x04 # CCID functionality
class TAG(object):
"""Tags for TLV data read from the YK4_CAPABILITIES slot"""
CAPA = 0x01 # capabilities
SERIAL = 0x02 # serial number

View File

@ -1,134 +0,0 @@
"""
module for creating frames of data that can be sent to a YubiKey
"""
# Copyright (c) 2010, Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
# functions
# classes
'YubiKeyFrame',
]
import struct
from . import yubico_util
from . import yubikey_defs
from . import yubico_exception
from .yubico_version import __version__
from .yubikey_defs import SLOT
class YubiKeyFrame:
"""
Class containing an YKFRAME (as defined in ykdef.h).
A frame is basically 64 bytes of data. When this is to be sent
to a YubiKey, it is put inside 10 USB HID feature reports. Each
feature report is 7 bytes of data plus 1 byte of sequencing and
flags.
"""
def __init__(self, command, payload=b''):
if not payload:
payload = b'\x00' * 64
if len(payload) != 64:
raise yubico_exception.InputError('payload must be empty or 64 bytes')
if not isinstance(payload, bytes):
raise yubico_exception.InputError('payload must be a bytestring')
self.payload = payload
self.command = command
self.crc = yubico_util.crc16(payload)
def __repr__(self):
return '<%s.%s instance at %s: %s>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self)),
self.command
)
def to_string(self):
"""
Return the frame as a 70 byte string.
"""
# From ykdef.h :
#
# // Frame structure
# #define SLOT_DATA_SIZE 64
# typedef struct {
# unsigned char payload[SLOT_DATA_SIZE];
# unsigned char slot;
# unsigned short crc;
# unsigned char filler[3];
# } YKFRAME;
filler = b''
return struct.pack('<64sBH3s',
self.payload, self.command, self.crc, filler)
def to_feature_reports(self, debug=False):
"""
Return the frame as an array of 8-byte parts, ready to be sent to a YubiKey.
"""
rest = self.to_string()
seq = 0
out = []
# When sending a frame to the YubiKey, we can (should) remove any
# 7-byte serie that only consists of '\x00', besides the first
# and last serie.
while rest:
this, rest = rest[:7], rest[7:]
if seq > 0 and rest:
# never skip first or last serie
if this != b'\x00\x00\x00\x00\x00\x00\x00':
this += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq)
out.append(self._debug_string(debug, this))
else:
this += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq)
out.append(self._debug_string(debug, this))
seq += 1
return out
def _debug_string(self, debug, data):
"""
Annotate a frames data, if debug is True.
"""
if not debug:
return data
if self.command in [
SLOT.CONFIG,
SLOT.CONFIG2,
SLOT.UPDATE1,
SLOT.UPDATE2,
SLOT.SWAP,
]:
# annotate according to config_st (see ykdef.h)
if yubico_util.ord_byte(data[-1]) == 0x80:
return (data, "FFFFFFF") # F = Fixed data (16 bytes)
if yubico_util.ord_byte(data[-1]) == 0x81:
return (data, "FFFFFFF")
if yubico_util.ord_byte(data[-1]) == 0x82:
return (data, "FFUUUUU") # U = UID (6 bytes)
if yubico_util.ord_byte(data[-1]) == 0x83:
return (data, "UKKKKKK") # K = Key (16 bytes)
if yubico_util.ord_byte(data[-1]) == 0x84:
return (data, "KKKKKKK")
if yubico_util.ord_byte(data[-1]) == 0x85:
return (data, "KKKAAAA") # A = Access code to set (6 bytes)
if yubico_util.ord_byte(data[-1]) == 0x86:
return (data, "AAlETCr") # l = Length of fixed field (1 byte)
# E = extFlags (1 byte)
# T = tktFlags (1 byte)
# C = cfgFlags (1 byte)
# r = RFU (2 bytes)
if yubico_util.ord_byte(data[-1]) == 0x87:
return (data, "rCRaaaa") # CR = CRC16 checksum (2 bytes)
# a = Access code to use (6 bytes)
if yubico_util.ord_byte(data[-1]) == 0x88:
return (data, 'aa')
# after payload
if yubico_util.ord_byte(data[-1]) == 0x89:
return (data, " Scr")
return (data, '')

View File

@ -1,350 +0,0 @@
"""
module for accessing a USB HID YubiKey NEO
"""
# Copyright (c) 2012 Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
'uri_identifiers',
# functions
# classes
'YubiKeyNEO_USBHID',
'YubiKeyNEO_USBHIDError'
]
import struct
import binascii
from .yubico_version import __version__
from .yubikey_defs import SLOT, MODE
from . import yubikey_usb_hid
from . import yubikey_base
from . import yubikey_frame
from . import yubico_exception
from . import yubico_util
# commands from ykdef.h
_ACC_CODE_SIZE = 6 # Size of access code to re-program device
_NDEF_DATA_SIZE = 54
# from nfcdef.h
_NDEF_URI_TYPE = ord('U')
_NDEF_TEXT_TYPE = ord('T')
# From nfcforum-ts-rtd-uri-1.0.pdf
uri_identifiers = [
(0x01, "http://www.",),
(0x02, "https://www.",),
(0x03, "http://",),
(0x04, "https://",),
(0x05, "tel:",),
(0x06, "mailto:",),
(0x07, "ftp://anonymous:anonymous@",),
(0x08, "ftp://ftp.",),
(0x09, "ftps://",),
(0x0a, "sftp://",),
(0x0b, "smb://",),
(0x0c, "nfs://",),
(0x0d, "ftp://",),
(0x0e, "dav://",),
(0x0f, "news:",),
(0x10, "telnet://",),
(0x11, "imap:",),
(0x12, "rtsp://",),
(0x13, "urn:",),
(0x14, "pop:",),
(0x15, "sip:",),
(0x16, "sips:",),
(0x17, "tftp:",),
(0x18, "btspp://",),
(0x19, "btl2cap://",),
(0x1a, "btgoep://",),
(0x1b, "tcpobex://",),
(0x1c, "irdaobex://",),
(0x1d, "file://",),
(0x1e, "urn:epc:id:",),
(0x1f, "urn:epc:tag:",),
(0x20, "urn:epc:pat:",),
(0x21, "urn:epc:raw:",),
(0x22, "urn:epc:",),
(0x23, "urn:nfc:",),
]
_NDEF_SLOTS = {
1: SLOT.NDEF,
2: SLOT.NDEF2
}
class YubiKeyNEO_USBHIDError(yubico_exception.YubicoError):
""" Exception raised for errors with the NEO USB HID communication. """
class YubiKeyNEO_USBHIDCapabilities(yubikey_usb_hid.YubiKeyUSBHIDCapabilities):
"""
Capabilities of current YubiKey NEO.
"""
def have_challenge_response(self, mode):
return self.version >= (3, 0, 0)
def have_configuration_slot(self, slot):
if self.version < (3, 0, 0):
return (slot == 1)
return slot in [1, 2]
def have_nfc_ndef(self, slot=1):
if self.version < (3, 0, 0):
return slot == 1
return slot in [1, 2]
def have_scanmap(self):
return self.version >= (3, 0, 0)
def have_device_config(self):
return self.version >= (3, 0, 0)
def have_usb_mode(self, mode):
if not self.have_device_config():
return False
mode &= ~MODE.FLAG_EJECT # Mask away eject flag
return mode in [0, 1, 2, 3, 4, 5, 6]
class YubiKeyNEO_USBHID(yubikey_usb_hid.YubiKeyUSBHID):
"""
Class for accessing a YubiKey NEO over USB HID.
The NEO is very similar to the original YubiKey (YubiKeyUSBHID)
but does add the NDEF "slot".
The NDEF is the tag the YubiKey emmits over it's NFC interface.
"""
model = 'YubiKey NEO'
description = 'YubiKey NEO'
_capabilities_cls = YubiKeyNEO_USBHIDCapabilities
def __init__(self, debug=False, skip=0, hid_device=None):
"""
Find and connect to a YubiKey NEO (USB HID).
Attributes :
skip -- number of YubiKeys to skip
debug -- True or False
"""
super(YubiKeyNEO_USBHID, self).__init__(debug, skip, hid_device)
if self.version_num() >= (2, 1, 4,) and \
self.version_num() <= (2, 1, 9,):
self.description = 'YubiKey NEO BETA'
elif self.version_num() < (3, 0, 0):
raise yubikey_base.YubiKeyVersionError("Incorrect version for %s" % self)
def write_ndef(self, ndef, slot=1):
"""
Write an NDEF tag configuration to the YubiKey NEO.
"""
if not self.capabilities.have_nfc_ndef(slot):
raise yubikey_base.YubiKeyVersionError("NDEF slot %i unsupported in %s" % (slot, self))
return self._device._write_config(ndef, _NDEF_SLOTS[slot])
def init_device_config(self, **kwargs):
return YubiKeyNEO_DEVICE_CONFIG(**kwargs)
def write_device_config(self, device_config):
"""
Write a DEVICE_CONFIG to the YubiKey NEO.
"""
if not self.capabilities.have_usb_mode(device_config._mode):
raise yubikey_base.YubiKeyVersionError("USB mode: %02x not supported for %s" % (device_config._mode, self))
return self._device._write_config(device_config, SLOT.DEVICE_CONFIG)
def write_scan_map(self, scanmap=None):
if not self.capabilities.have_scanmap():
raise yubikey_base.YubiKeyVersionError("Scanmap not supported in %s" % self)
return self._device._write_config(YubiKeyNEO_SCAN_MAP(scanmap), SLOT.SCAN_MAP)
class YubiKeyNEO_NDEF(object):
"""
Class allowing programming of a YubiKey NEO NDEF.
"""
ndef_type = _NDEF_URI_TYPE
ndef_str = None
access_code = yubico_util.chr_byte(0x0) * _ACC_CODE_SIZE
# For _NDEF_URI_TYPE
ndef_uri_rt = 0x0 # No prepending
# For _NDEF_TEXT_TYPE
ndef_text_lang = b'en'
ndef_text_enc = 'UTF-8'
def __init__(self, data, access_code = None):
self.ndef_str = data
if access_code is not None:
self.access_code = access_code
def text(self, encoding = 'UTF-8', language = 'en'):
"""
Configure parameters for NDEF type TEXT.
@param encoding: The encoding used. Should be either 'UTF-8' or 'UTF16'.
@param language: ISO/IANA language code (see RFC 3066).
"""
self.ndef_type = _NDEF_TEXT_TYPE
self.ndef_text_lang = language
self.ndef_text_enc = encoding
return self
def type(self, url = False, text = False, other = None):
"""
Change the NDEF type.
"""
if (url, text, other) == (True, False, None):
self.ndef_type = _NDEF_URI_TYPE
elif (url, text, other) == (False, True, None):
self.ndef_type = _NDEF_TEXT_TYPE
elif (url, text, type(other)) == (False, False, int):
self.ndef_type = other
else:
raise YubiKeyNEO_USBHIDError("Bad or conflicting NDEF type specified")
return self
def to_string(self):
"""
Return the current NDEF as a string (always 64 bytes).
"""
data = self.ndef_str
if self.ndef_type == _NDEF_URI_TYPE:
data = self._encode_ndef_uri_type(data)
elif self.ndef_type == _NDEF_TEXT_TYPE:
data = self._encode_ndef_text_params(data)
if len(data) > _NDEF_DATA_SIZE:
raise YubiKeyNEO_USBHIDError("NDEF payload too long")
# typedef struct {
# unsigned char len; // Payload length
# unsigned char type; // NDEF type specifier
# unsigned char data[NDEF_DATA_SIZE]; // Payload size
# unsigned char curAccCode[ACC_CODE_SIZE]; // Access code
# } YKNDEF;
#
fmt = '< B B %ss %ss' % (_NDEF_DATA_SIZE, _ACC_CODE_SIZE)
first = struct.pack(fmt,
len(data),
self.ndef_type,
data.ljust(_NDEF_DATA_SIZE, b'\0'),
self.access_code,
)
#crc = 0xffff - yubico_util.crc16(first)
#second = first + struct.pack('<H', crc) + self.unlock_code
return first
def to_frame(self, slot=SLOT.NDEF):
"""
Return the current configuration as a YubiKeyFrame object.
"""
data = self.to_string()
payload = data.ljust(64, b'\0')
return yubikey_frame.YubiKeyFrame(command = slot, payload = payload)
def _encode_ndef_uri_type(self, data):
"""
Implement NDEF URI Identifier Code.
This is a small hack to replace some well known prefixes (such as http://)
with a one byte code. If the prefix is not known, 0x00 is used.
"""
t = 0x0
for (code, prefix) in uri_identifiers:
if data[:len(prefix)].decode('latin-1').lower() == prefix:
t = code
data = data[len(prefix):]
break
data = yubico_util.chr_byte(t) + data
return data
def _encode_ndef_text_params(self, data):
"""
Prepend language and enconding information to data, according to
nfcforum-ts-rtd-text-1-0.pdf
"""
status = len(self.ndef_text_lang)
if self.ndef_text_enc == 'UTF16':
status = status & 0b10000000
return yubico_util.chr_byte(status) + self.ndef_text_lang + data
class YubiKeyNEO_DEVICE_CONFIG(object):
"""
Class allowing programming of a YubiKey NEO DEVICE_CONFIG.
"""
_mode = MODE.OTP
_cr_timeout = 0
_auto_eject_time = 0
def __init__(self, mode=MODE.OTP):
self._mode = mode
def cr_timeout(self, timeout = 0):
"""
Configure the challenge-response timeout in seconds.
"""
self._cr_timeout = timeout
return self
def auto_eject_time(self, auto_eject_time = 0):
"""
Configure the auto eject time in 10x seconds.
"""
self._auto_eject_time = auto_eject_time
return self
def to_string(self):
"""
Return the current DEVICE_CONFIG as a string (always 4 bytes).
"""
fmt = '<BBH'
first = struct.pack(
fmt,
self._mode,
self._cr_timeout,
self._auto_eject_time
)
#crc = 0xffff - yubico_util.crc16(first)
#second = first + struct.pack('<H', crc)
return first
def to_frame(self, slot=SLOT.DEVICE_CONFIG):
"""
Return the current configuration as a YubiKeyFrame object.
"""
data = self.to_string()
payload = data.ljust(64, b'\0')
return yubikey_frame.YubiKeyFrame(command=slot, payload=payload)
class YubiKeyNEO_SCAN_MAP(object):
"""
Class allowing programming of a YubiKey NEO scan map.
"""
def __init__(self, scanmap=None):
if scanmap:
if scanmap.startswith(b'h:'):
scanmap = binascii.unhexlify(scanmap[2:])
if len(scanmap) != 45:
raise yubico_exception.InputError('Scan map must be exactly 45 bytes')
self.scanmap = scanmap
def to_frame(self, slot=SLOT.SCAN_MAP):
"""
Return the current configuration as a YubiKeyFrame object.
"""
payload = self.scanmap.ljust(64, b'\0')
return yubikey_frame.YubiKeyFrame(command=slot, payload=payload)

View File

@ -1,595 +0,0 @@
"""
module for accessing a USB HID YubiKey
"""
# Copyright (c) 2010, 2011, 2012 Yubico AB
# See the file COPYING for licence statement.
__all__ = [
# constants
# functions
# classes
'YubiKeyUSBHID',
'YubiKeyUSBHIDError',
'YubiKeyUSBHIDStatus',
]
from .yubico_version import __version__
from . import yubico_util
from . import yubico_exception
from . import yubikey_frame
from . import yubikey_config
from . import yubikey_defs
from . import yubikey_base
from .yubikey_defs import SLOT, YUBICO_VID, PID
from .yubikey_base import YubiKey
import struct
import time
import sys
import usb
# Various USB/HID parameters
_USB_TYPE_CLASS = (0x01 << 5)
_USB_RECIP_INTERFACE = 0x01
_USB_ENDPOINT_IN = 0x80
_USB_ENDPOINT_OUT = 0x00
_HID_GET_REPORT = 0x01
_HID_SET_REPORT = 0x09
_USB_TIMEOUT_MS = 2000
# from ykcore_backend.h
_FEATURE_RPT_SIZE = 8
_REPORT_TYPE_FEATURE = 0x03
# dict used to select command for mode+slot in _challenge_response
_CMD_CHALLENGE = {'HMAC': {1: SLOT.CHAL_HMAC1, 2: SLOT.CHAL_HMAC2},
'OTP': {1: SLOT.CHAL_OTP1, 2: SLOT.CHAL_OTP2},
}
class YubiKeyUSBHIDError(yubico_exception.YubicoError):
""" Exception raised for errors with the USB HID communication. """
class YubiKeyUSBHIDCapabilities(yubikey_base.YubiKeyCapabilities):
"""
Capture the capabilities of the various versions of YubiKeys.
Overrides just the functions from YubiKeyCapabilities() that are available
in one or more versions, leaving the other ones at False through default_answer.
"""
def __init__(self, model, version, default_answer):
super(YubiKeyUSBHIDCapabilities, self).__init__(
model=model,
version=version,
default_answer=default_answer)
def have_yubico_OTP(self):
""" Yubico OTP support has always been available in the standard YubiKey. """
return True
def have_OATH(self, mode):
""" OATH HOTP was introduced in YubiKey 2.2. """
if mode not in ['HOTP']:
return False
return (self.version >= (2, 1, 0,))
def have_challenge_response(self, mode):
""" Challenge-response was introduced in YubiKey 2.2. """
if mode not in ['HMAC', 'OTP']:
return False
return (self.version >= (2, 2, 0,))
def have_serial_number(self):
""" Reading serial number was introduced in YubiKey 2.2, but depends on extflags set too. """
return (self.version >= (2, 2, 0,))
def have_ticket_flag(self, flag):
return flag.is_compatible(model = self.model, version = self.version)
def have_config_flag(self, flag):
return flag.is_compatible(model = self.model, version = self.version)
def have_extended_flag(self, flag):
return flag.is_compatible(model = self.model, version = self.version)
def have_extended_scan_code_mode(self):
return (self.version >= (2, 0, 0,))
def have_shifted_1_mode(self):
return (self.version >= (2, 0, 0,))
def have_configuration_slot(self, slot):
return (slot in [1, 2])
class YubiKeyHIDDevice(object):
"""
High-level wrapper for low-level HID commands for a HID based YubiKey.
"""
def __init__(self, debug=False, skip=0):
"""
Find and connect to a YubiKey (USB HID).
Attributes :
skip -- number of YubiKeys to skip
debug -- True or False
"""
self.debug = debug
self._usb_handle = None
if not self._open(skip):
raise YubiKeyUSBHIDError('YubiKey USB HID initialization failed')
self.status()
def status(self):
"""
Poll YubiKey for status.
"""
data = self._read()
self._status = YubiKeyUSBHIDStatus(data)
return self._status
def __del__(self):
try:
if self._usb_handle:
self._close()
except (IOError, AttributeError):
pass
def _write_config(self, cfg, slot):
""" Write configuration to YubiKey. """
old_pgm_seq = self._status.pgm_seq
frame = cfg.to_frame(slot=slot)
self._debug("Writing %s frame :\n%s\n" % \
(yubikey_config.command2str(frame.command), cfg))
self._write(frame)
self._waitfor_clear(yubikey_defs.SLOT_WRITE_FLAG)
# make sure we have a fresh pgm_seq value
self.status()
self._debug("Programmed slot %i, sequence %i -> %i\n" % (slot, old_pgm_seq, self._status.pgm_seq))
cfgs = self._status.valid_configs()
if not cfgs and self._status.pgm_seq == 0:
return
if self._status.pgm_seq == old_pgm_seq + 1:
return
raise YubiKeyUSBHIDError('YubiKey programming failed (seq %i not increased (%i))' % \
(old_pgm_seq, self._status.pgm_seq))
def _read_response(self, may_block=False):
""" Wait for a response to become available, and read it. """
# wait for response to become available
res = self._waitfor_set(yubikey_defs.RESP_PENDING_FLAG, may_block)[:7]
# continue reading while response pending is set
while True:
this = self._read()
flags = yubico_util.ord_byte(this[7])
if flags & yubikey_defs.RESP_PENDING_FLAG:
seq = flags & 0b00011111
if res and (seq == 0):
break
res += this[:7]
else:
break
self._write_reset()
return res
def _read(self):
""" Read a USB HID feature report from the YubiKey. """
request_type = _USB_TYPE_CLASS | _USB_RECIP_INTERFACE | _USB_ENDPOINT_IN
value = _REPORT_TYPE_FEATURE << 8 # apparently required for YubiKey 1.3.2, but not 2.2.x
recv = self._usb_handle.controlMsg(request_type,
_HID_GET_REPORT,
_FEATURE_RPT_SIZE,
value = value,
timeout = _USB_TIMEOUT_MS)
if len(recv) != _FEATURE_RPT_SIZE:
self._debug("Failed reading %i bytes (got %i) from USB HID YubiKey.\n"
% (_FEATURE_RPT_SIZE, recv))
raise YubiKeyUSBHIDError('Failed reading from USB HID YubiKey')
data = b''.join(yubico_util.chr_byte(c) for c in recv)
self._debug("READ : %s" % (yubico_util.hexdump(data, colorize=True)))
return data
def _write(self, frame):
"""
Write a YubiKeyFrame to the USB HID.
Includes polling for YubiKey readiness before each write.
"""
for data in frame.to_feature_reports(debug=self.debug):
debug_str = None
if self.debug:
(data, debug_str) = data
# first, we ensure the YubiKey will accept a write
self._waitfor_clear(yubikey_defs.SLOT_WRITE_FLAG)
self._raw_write(data, debug_str)
return True
def _write_reset(self):
"""
Reset read mode by issuing a dummy write.
"""
data = b'\x00\x00\x00\x00\x00\x00\x00\x8f'
self._raw_write(data)
self._waitfor_clear(yubikey_defs.SLOT_WRITE_FLAG)
return True
def _raw_write(self, data, debug_str = None):
"""
Write data to YubiKey.
"""
if self.debug:
if not debug_str:
debug_str = ''
hexdump = yubico_util.hexdump(data, colorize=True)[:-1] # strip LF
self._debug("WRITE : %s %s\n" % (hexdump, debug_str))
request_type = _USB_TYPE_CLASS | _USB_RECIP_INTERFACE | _USB_ENDPOINT_OUT
value = _REPORT_TYPE_FEATURE << 8 # apparently required for YubiKey 1.3.2, but not 2.2.x
sent = self._usb_handle.controlMsg(request_type,
_HID_SET_REPORT,
data,
value = value,
timeout = _USB_TIMEOUT_MS)
if sent != _FEATURE_RPT_SIZE:
self.debug("Failed writing %i bytes (wrote %i) to USB HID YubiKey.\n"
% (_FEATURE_RPT_SIZE, sent))
raise YubiKeyUSBHIDError('Failed talking to USB HID YubiKey')
return sent
def _waitfor_clear(self, mask, may_block=False):
"""
Wait for the YubiKey to turn OFF the bits in 'mask' in status responses.
Returns the 8 bytes last read.
"""
return self._waitfor('nand', mask, may_block)
def _waitfor_set(self, mask, may_block=False):
"""
Wait for the YubiKey to turn ON the bits in 'mask' in status responses.
Returns the 8 bytes last read.
"""
return self._waitfor('and', mask, may_block)
def _waitfor(self, mode, mask, may_block, timeout=2):
"""
Wait for the YubiKey to either turn ON or OFF certain bits in the status byte.
mode is either 'and' or 'nand'
timeout is a number of seconds (precision about ~0.5 seconds)
"""
finished = False
sleep = 0.01
# After six sleeps, we've slept 0.64 seconds.
wait_num = (timeout * 2) - 1 + 6
resp_timeout = False # YubiKey hasn't indicated RESP_TIMEOUT (yet)
while not finished:
time.sleep(sleep)
this = self._read()
flags = yubico_util.ord_byte(this[7])
if flags & yubikey_defs.RESP_TIMEOUT_WAIT_FLAG:
if not resp_timeout:
resp_timeout = True
seconds_left = flags & yubikey_defs.RESP_TIMEOUT_WAIT_MASK
self._debug("Device indicates RESP_TIMEOUT (%i seconds left)\n" \
% (seconds_left))
if may_block:
# calculate new wait_num - never more than 20 seconds
seconds_left = min(20, seconds_left)
wait_num = (seconds_left * 2) - 1 + 6
if mode is 'nand':
if not flags & mask == mask:
finished = True
else:
self._debug("Status %s (0x%x) has not cleared bits %s (0x%x)\n"
% (bin(flags), flags, bin(mask), mask))
elif mode is 'and':
if flags & mask == mask:
finished = True
else:
self._debug("Status %s (0x%x) has not set bits %s (0x%x)\n"
% (bin(flags), flags, bin(mask), mask))
else:
assert()
if not finished:
wait_num -= 1
if wait_num == 0:
if mode is 'nand':
reason = 'Timed out waiting for YubiKey to clear status 0x%x' % mask
else:
reason = 'Timed out waiting for YubiKey to set status 0x%x' % mask
raise yubikey_base.YubiKeyTimeout(reason)
sleep = min(sleep + sleep, 0.5)
else:
return this
def _open(self, skip=0):
""" Perform HID initialization """
usb_device = self._get_usb_device(skip)
if usb_device:
usb_conf = usb_device.configurations[0]
self._usb_int = usb_conf.interfaces[0][0]
else:
raise YubiKeyUSBHIDError('No USB YubiKey found')
try:
self._usb_handle = usb_device.open()
self._usb_handle.detachKernelDriver(0)
except Exception as error:
if 'could not detach kernel driver from interface' in str(error):
self._debug('The in-kernel-HID driver has already been detached\n')
else:
self._debug("detachKernelDriver not supported!\n")
try:
self._usb_handle.setConfiguration(1)
except usb.USBError:
self._debug("Unable to set configuration, ignoring...\n")
self._usb_handle.claimInterface(self._usb_int)
return True
def _close(self):
"""
Release the USB interface again.
"""
self._usb_handle.releaseInterface()
try:
# If we're using PyUSB >= 1.0 we can re-attach the kernel driver here.
self._usb_handle.dev.attach_kernel_driver(0)
except:
pass
self._usb_int = None
self._usb_handle = None
return True
def _get_usb_device(self, skip=0):
"""
Get YubiKey USB device.
Optionally allows you to skip n devices, to support multiple attached YubiKeys.
"""
try:
# PyUSB >= 1.0, this is a workaround for a problem with libusbx
# on Windows.
import usb.core
import usb.legacy
devices = [usb.legacy.Device(d) for d in usb.core.find(
find_all=True, idVendor=YUBICO_VID)]
except ImportError:
# Using PyUsb < 1.0.
import usb
devices = [d for bus in usb.busses() for d in bus.devices]
for device in devices:
if device.idVendor == YUBICO_VID:
if device.idProduct in PID.all(otp=True):
if skip == 0:
return device
skip -= 1
return None
def _debug(self, out, print_prefix=True):
""" Print out to stderr, if debugging is enabled. """
if self.debug:
if print_prefix:
pre = self.__class__.__name__
if hasattr(self, 'debug_prefix'):
pre = getattr(self, 'debug_prefix')
sys.stderr.write("%s: " % pre)
sys.stderr.write(out)
class YubiKeyUSBHID(YubiKey):
"""
Class for accessing a YubiKey over USB HID.
This class is for communicating specifically with standard YubiKeys
(USB vendor id = 0x1050, product id = 0x10) using USB HID.
There is another class for the YubiKey NEO BETA, even though that
product also goes by product id 0x10 for the BETA versions. The
expectation is that the final YubiKey NEO will have it's own product id.
Tested with YubiKey versions 1.3 and 2.2.
"""
model = 'YubiKey'
description = 'YubiKey (or YubiKey NANO)'
_capabilities_cls = YubiKeyUSBHIDCapabilities
def __init__(self, debug=False, skip=0, hid_device=None):
"""
Find and connect to a YubiKey (USB HID).
Attributes :
skip -- number of YubiKeys to skip
debug -- True or False
"""
super(YubiKeyUSBHID, self).__init__(debug)
if hid_device is None:
self._device = YubiKeyHIDDevice(debug, skip)
else:
self._device = hid_device
self.capabilities = \
self._capabilities_cls(model=self.model,
version=self.version_num(),
default_answer=False)
def __repr__(self):
return '<%s instance at %s: YubiKey version %s>' % (
self.__class__.__name__,
hex(id(self)),
self.version()
)
def __str__(self):
return '%s (%s)' % (self.model, self.version())
def status(self):
"""
Poll YubiKey for status.
"""
return self._device.status()
def version_num(self):
""" Get the YubiKey version as a tuple (major, minor, build). """
return self._device._status.ykver()
def version(self):
""" Get the YubiKey version. """
return self._device._status.version()
def serial(self, may_block=True):
""" Get the YubiKey serial number (requires YubiKey 2.2). """
if not self.capabilities.have_serial_number():
raise yubikey_base.YubiKeyVersionError("Serial number unsupported in YubiKey %s" % self.version() )
return self._read_serial(may_block)
def challenge_response(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True):
""" Issue a challenge to the YubiKey and return the response (requires YubiKey 2.2). """
if not self.capabilities.have_challenge_response(mode):
raise yubikey_base.YubiKeyVersionError("%s challenge-response unsupported in YubiKey %s" % (mode, self.version()) )
return self._challenge_response(challenge, mode, slot, variable, may_block)
def init_config(self, **kw):
""" Get a configuration object for this type of YubiKey. """
return YubiKeyConfigUSBHID(ykver=self.version_num(), \
capabilities = self.capabilities, \
**kw)
def write_config(self, cfg, slot=1):
""" Write a configuration to the YubiKey. """
cfg_req_ver = cfg.version_required()
if cfg_req_ver > self.version_num():
raise yubikey_base.YubiKeyVersionError('Configuration requires YubiKey version %i.%i (this is %s)' % \
(cfg_req_ver[0], cfg_req_ver[1], self.version()))
if not self.capabilities.have_configuration_slot(slot):
raise YubiKeyUSBHIDError("Can't write configuration to slot %i" % (slot))
return self._device._write_config(cfg, slot)
def _read_serial(self, may_block):
""" Read the serial number from a YubiKey > 2.2. """
frame = yubikey_frame.YubiKeyFrame(command = SLOT.DEVICE_SERIAL)
self._device._write(frame)
response = self._device._read_response(may_block=may_block)
if not yubico_util.validate_crc16(response[:6]):
raise YubiKeyUSBHIDError("Read from device failed CRC check")
# the serial number is big-endian, although everything else is little-endian
serial = struct.unpack('>lxxx', response)
return serial[0]
def _challenge_response(self, challenge, mode, slot, variable, may_block):
""" Do challenge-response with a YubiKey > 2.0. """
# Check length and pad challenge if appropriate
if mode == 'HMAC':
if len(challenge) > yubikey_defs.SHA1_MAX_BLOCK_SIZE:
raise yubico_exception.InputError('Mode HMAC challenge too big (%i/%i)' \
% (yubikey_defs.SHA1_MAX_BLOCK_SIZE, len(challenge)))
if len(challenge) < yubikey_defs.SHA1_MAX_BLOCK_SIZE:
pad_with = b'\0'
if variable and challenge[-1:] == pad_with:
pad_with = b'\xff'
challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, pad_with)
response_len = yubikey_defs.SHA1_DIGEST_SIZE
elif mode == 'OTP':
if len(challenge) != yubikey_defs.UID_SIZE:
raise yubico_exception.InputError('Mode OTP challenge must be %i bytes (got %i)' \
% (yubikey_defs.UID_SIZE, len(challenge)))
challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, b'\0')
response_len = 16
else:
raise yubico_exception.InputError('Invalid mode supplied (%s, valid values are HMAC and OTP)' \
% (mode))
try:
command = _CMD_CHALLENGE[mode][slot]
except:
raise yubico_exception.InputError('Invalid slot specified (%s)' % (slot))
frame = yubikey_frame.YubiKeyFrame(command=command, payload=challenge)
self._device._write(frame)
response = self._device._read_response(may_block=may_block)
if not yubico_util.validate_crc16(response[:response_len + 2]):
raise YubiKeyUSBHIDError("Read from device failed CRC check")
return response[:response_len]
class YubiKeyUSBHIDStatus(object):
""" Class to represent the status information we get from the YubiKey. """
CONFIG1_VALID = 0x01 # Bit in touchLevel indicating that configuration 1 is valid (from firmware 2.1)
CONFIG2_VALID = 0x02 # Bit in touchLevel indicating that configuration 2 is valid (from firmware 2.1)
def __init__(self, data):
# From ykdef.h :
#
# struct status_st {
# unsigned char versionMajor; /* Firmware version information */
# unsigned char versionMinor;
# unsigned char versionBuild;
# unsigned char pgmSeq; /* Programming sequence number. 0 if no valid configuration */
# unsigned short touchLevel; /* Level from touch detector */
# };
fmt = '<x BBB B H B'
self.version_major, \
self.version_minor, \
self.version_build, \
self.pgm_seq, \
self.touch_level, \
self.flags = struct.unpack(fmt, data)
def __repr__(self):
valid_str = ''
flags_str = ''
if self.ykver() >= (2,1,0):
valid_str = ", valid=%s" % (self.valid_configs())
if self.flags:
flags_str = " (flags 0x%x)" % (self.flags)
return '<%s instance at %s: YubiKey version %s, pgm_seq=%i, touch_level=%i%s%s>' % (
self.__class__.__name__,
hex(id(self)),
self.version(),
self.pgm_seq,
self.touch_level,
valid_str,
flags_str,
)
def ykver(self):
""" Returns a tuple with the (major, minor, build) version of the YubiKey firmware. """
return (self.version_major, self.version_minor, self.version_build)
def version(self):
""" Return the YubiKey firmware version as a string. """
version = "%d.%d.%d" % (self.ykver())
return version
def valid_configs(self):
""" Return a list of slots having a valid configurtion. Requires firmware 2.1. """
if self.ykver() < (2,1,0):
raise YubiKeyUSBHIDError('Valid configs unsupported in firmware %s' % (self.version()))
res = []
if self.touch_level & self.CONFIG1_VALID == self.CONFIG1_VALID:
res.append(1)
if self.touch_level & self.CONFIG2_VALID == self.CONFIG2_VALID:
res.append(2)
return res
class YubiKeyConfigUSBHID(yubikey_config.YubiKeyConfig):
"""
Configuration class for USB HID YubiKeys.
"""
def __init__(self, ykver, capabilities = None, **kw):
super(YubiKeyConfigUSBHID, self).__init__(ykver=ykver, capabilities=capabilities, **kw)