yubikey-piv-manager-1.3.0/0000755000076500000240000000000012755276415015274 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/ChangeLog0000644000076500000240000010300412755276415017044 0ustar dagstaff000000000000002016-08-18 Dag Heyman * NEWS, pivman/__init__.py: Update version and NEWS 2016-08-18 Dag Heyman * pivman/controller.py: Change certificate expiration to 30 years 2016-08-17 Dag Heyman * NEWS: Update NEWS 2016-08-16 Dain Nilsson * pivman/controller.py, setup.py: Remove PyCrypto. 2016-08-16 Dag Heyman * NEWS: Update NEWS 2016-08-16 Dag Heyman * README: Update README 2016-08-15 Dag Heyman * pivman/controller.py: Add long valid period for default certs For certificates generated on initialization, set the expiration date about 100 years in the future. 2016-08-15 Dain Nilsson * .travis.yml: Remove python 2.6 from travis. 2016-08-15 Dain Nilsson * pivman/__main__.py: Fix library version display on py3. 2016-08-15 Dag Heyman * pivman/view/init_dialog.py: Fix the authentication cert checkbox 2016-08-15 Dag Heyman * pivman/view/generate_dialog.py: Enable expiration date for ssc only 2016-08-15 Dag Heyman * pivman/controller.py, pivman/messages.py, pivman/piv.py, pivman/piv_cmd.py, pivman/view/generate_dialog.py: Add expiration date option When generating keys and certificates add field for expiration date in dialog. 2016-08-15 Dain Nilsson * .travis.yml: Remove Python 3.5 and pypi from travis. 2016-08-15 Dain Nilsson * pivman/controller.py: Pass active window reference instead of doing new lookup. 2016-08-15 Dain Nilsson * pivman/piv.py, pivman/view/utils.py, pivman/watcher.py: Various python 3 fixes. 2016-08-15 Dain Nilsson * pivman/controller.py, pivman/piv.py, pivman/piv_cmd.py, pivman/view/cert.py, pivman/view/init_dialog.py, pivman/view/main.py, pivman/view/set_key_dialog.py: Made string handling python3 compatible. 2016-08-15 Dain Nilsson * pivman/controller.py, pivman/piv.py, pivman/utils.py: Replace usage of chr/ord. 2016-08-15 Dain Nilsson * pivman/__main__.py, pivman/view/settings_dialog.py, pivman/watcher.py: Use print function. 2016-08-12 Dain Nilsson * .travis.yml: Added initial .travis.yml file. 2016-08-12 Dain Nilsson * pivman/__main__.py, pivman/controller.py, pivman/messages.py, pivman/piv_cmd.py, pivman/view/cert.py, setup.py: Flake8 formatting fixes. 2016-08-12 Dain Nilsson * setup.cfg: Added configuration for flake8. 2016-08-12 Dag Heyman * pivman/view/cert.py: Support both key and cert in .pem file Add support for a combined certificate and private key in a .pem file. Closes #6. 2016-08-12 Dag Heyman * pivman/messages.py: Formatting in messages.py 2016-08-12 Dag Heyman * pivman/messages.py: Add a space 2016-08-12 Dag Heyman * pivman/messages.py: Update confirmation message 2016-08-12 Dag Heyman * : Merge pull request #14 from Yubico/certutil-fix Use certutil -ping to find CA 2016-08-11 Dag Heyman * pivman/utils.py: Use certutil -ping to find CA Use the -ping option for the certutil tool to avoid problems with locales. Closes #5. 2016-08-11 Dag Heyman * pivman/controller.py, pivman/messages.py, pivman/view/init_dialog.py: Authentication cert on init by default Check the authenticaton cert checkbox in the initialization wizard. Also remove the word 'default'. 2016-08-11 Dag Heyman * vendor/yubicommon: Update yubicommon 2016-08-11 Dag Heyman * : Merge pull request #12 from Yubico/default-cert Add option for default certificate 2016-08-10 Dag Heyman * .gitignore: Add lib/ to .gitignore 2016-08-10 Dag Heyman * README, setup.py, test/__init__.py: Remove reference to nosetest 2016-08-10 Dag Heyman * pivman/controller.py, pivman/messages.py, pivman/view/init_dialog.py: Add option for default certificate When initializing a new YubiKey, give the user the option to generate a default certificate in the authentication slot. 2016-08-10 Dain Nilsson * pivman/storage.py: Show touch policy for generate key by default. 2016-07-01 Dain Nilsson * pivman/piv.py: Require ykpiv >= 1.2.0 2016-07-01 Dain Nilsson * pivman/controller.py: Fix PUK invalidation. 2016-07-01 Dain Nilsson * pivman/piv.py: Set CCC when unset. 2016-07-01 Dain Nilsson * pivman/controller.py, pivman/messages.py, pivman/view/init_dialog.py, pivman/view/set_pin_dialog.py: Minimum PIN length is now 6. 2016-07-01 Dain Nilsson * pivman/libykpiv.py, pivman/piv.py, vendor/yubicommon: Update yubicommon. 2016-07-01 Dain Nilsson * NEWS, pivman/__init__.py: Bumped version. 2015-12-08 Dain Nilsson * pivman/__init__.py: Bumped version. 2015-12-08 Dain Nilsson * NEWS, pivman/controller.py: Improved error message for too large certs. 2015-12-08 Dain Nilsson * vendor/yubicommon: Updated yubicommon. 2015-12-07 Dain Nilsson * NEWS, pivman/__init__.py: Prepare release. 2015-12-07 Dain Nilsson * NEWS, pivman/__init__.py: Bump version. 2015-12-07 Dain Nilsson * doc/Settings_and_Group_Policy.adoc: Update documentation for new settings. 2015-12-07 Dain Nilsson * NEWS, pivman/storage.py, pivman/view/cert.py, pivman/view/generate_dialog.py, pivman/view/usage_policy_dialog.py, vendor/yubicommon: Made PIN/Touch policy options configurable, and disabled by default. 2015-12-07 Dain Nilsson * pivman/__main__.py: Ensure only a single instance of the application. 2015-12-02 Dain Nilsson * NEWS: Updated NEWS. 2015-12-02 Dain Nilsson * pivman/__init__.py: Bumped version. 2015-12-02 Dain Nilsson * pivman/__main__.py, vendor/yubicommon: Added package version. 2015-11-16 Dain Nilsson * NEWS, pivman/__init__.py: Update version and NEWS for release. 2015-11-13 Dain Nilsson * NEWS: Updated NEWS. 2015-11-13 Dain Nilsson * pivman/controller.py, pivman/piv.py, pivman/view/cert.py, pivman/view/generate_dialog.py, pivman/view/init_dialog.py, pivman/view/set_key_dialog.py, pivman/view/set_pin_dialog.py: Attempt to reconnect to device if lost before using. 2015-11-12 Dain Nilsson * README: Updated executable build instructions in README. 2015-11-12 Dain Nilsson * resources/osx-installer.pkgproj, resources/win-installer.nsi: Updated Win/OSX installers. 2015-11-12 Dain Nilsson * NEWS, pivman/__init__.py: Bumped version post release. 2015-11-12 Dain Nilsson * NEWS, pivman/__init__.py: Prepare release. 2015-11-12 Dain Nilsson * pivman/piv.py: Require libykpiv >= 1.1.0 2015-11-11 Dain Nilsson * doc/Settings_and_Group_Policy.adoc, pivman/controller.py: Added "default" to pin_policy. An explicit value is needed as opposed to omission to be able to enforce use of the default value. 2015-11-11 Dain Nilsson * pivman/controller.py, pivman/storage.py, vendor/yubicommon: Use yubicommon Qt Settings. Remove unused device attributes. 2015-11-10 Dain Nilsson * .gitignore, .gitmodules, MANIFEST.in, man/pivman.1, pivman/__main__.py, pivman/libloader.py, pivman/libykpiv.py, pivman/main.py, pivman/view/init_dialog.py, pivman/view/main.py, pivman/view/set_key_dialog.py, pivman/view/set_pin_dialog.py, pivman/view/settings_dialog.py, pivman/view/usage_policy_dialog.py, pivman/worker.py, pivman/yubicommon, qt_resources.py, release.py, resources/osx-installer.pkgproj, resources/osx-patch-ccid, resources/pivman.nsi, resources/pivman.pkgproj, resources/pivman.spec, resources/qt.conf, resources/win-installer.nsi, scripts/pivman, scripts/pivman.1, setup.py, vendor/yubicommon: Use yubicommon. 2015-11-10 Dain Nilsson * NEWS, pivman/__init__.py: Updated NEWS and bumped version. 2015-11-10 Dain Nilsson * doc/Settings_and_Group_Policy.adoc: Added documentation for touch policy setting. 2015-11-10 Dain Nilsson * pivman/controller.py, pivman/libykpiv.py, pivman/messages.py, pivman/piv.py, pivman/piv_cmd.py, pivman/storage.py, pivman/view/cert.py, pivman/view/generate_dialog.py, pivman/view/usage_policy_dialog.py: Added new features available in libykpiv1. 2015-11-10 Dain Nilsson * pivman/piv.py, pivman/piv_cmd.py: Updates for libykpiv1. 2015-05-18 Dain Nilsson * NEWS, pivman/__init__.py: Update NEWS and version. 2015-09-10 Henrik Stråth * README: Mentioned minimal OS X version in README 2015-05-18 Dain Nilsson * NEWS, pivman/view/main.py: Don't save window position. 2015-05-18 Dain Nilsson * resources/pivman.nsi, resources/pivman.spec: Updated Windows packaging files. 2015-05-08 Dain Nilsson * NEWS, pivman/piv.py: Fix default card reader pattern. 2015-04-24 Dain Nilsson * resources/osx-patch-ccid, resources/pivman.pkgproj: Add CCID patch to OS X installer. 2015-04-22 Dain Nilsson * pivman/controller.py: Rename constant. 2015-04-16 Dain Nilsson * MANIFEST.in: Add screenshot.png to release tar. 2015-04-16 Dain Nilsson * NEWS, pivman/__init__.py: Updated NEWS and version for release. 2015-04-16 Dain Nilsson * pivman/view/cert.py, pivman/view/manage.py: Don't move windows after creating them. 2015-04-16 Dain Nilsson * qt_resources.py, release.py: Re-license release.py and qt_resources.py 2015-04-15 Henrik Stråth * README: Added screenshot to README 2015-04-15 Dain Nilsson * resources/pivman.pkgproj: Added Packages project for OSX. 2015-04-15 Dain Nilsson * resources/pivman.xpm: Added XPM icon. 2015-04-15 Dain Nilsson * pivman/controller.py: Removed unused function. 2015-04-14 Dain Nilsson * NEWS, pivman/__init__.py: Updated version and NEWS for release. 2015-04-14 Dain Nilsson * MANIFEST.in: Add doc/*.adoc to manifest. 2015-04-14 Dain Nilsson * README: Use https for link in README. 2015-04-14 Dain Nilsson * doc/Settings_and_Group_Policy.adoc: Don't use horizontal definition lists ;p 2015-04-14 Dain Nilsson * doc/Settings_and_Group_Policy.adoc: Use horizontal definition lists. 2015-04-14 Dain Nilsson * doc/Device_Setup.adoc, doc/PIN_and_Management_Key.adoc: Fixed typos in documentation. 2015-04-13 Dain Nilsson * doc/Settings_and_Group_Policy.adoc: Added missing spaces to titles. 2015-04-13 Dain Nilsson * doc/Certificates.adoc, doc/Device_Setup.adoc, doc/PIN_and_Management_Key.adoc, doc/Settings_and_Group_Policy.adoc: Added documentation. 2015-04-13 Dain Nilsson * pivman/view/cert.py: Remove trailing "," from .pem extension in export dialog. 2015-04-10 Dain Nilsson * pivman/view/main.py: Refresh after settings change. 2015-04-10 Dain Nilsson * pivman/controller.py, pivman/view/manage.py, pivman/view/set_pin_dialog.py: Fix issue with crashing when changing PIN. 2015-04-09 Dain Nilsson * scripts/pivman.1: Added man page. 2015-04-09 Dain Nilsson * NEWS: Added NEWS. 2015-04-09 Dain Nilsson * README.adoc: Added README.adoc symlink for Github. 2015-04-09 Dain Nilsson * README: Fix README. 2015-04-09 Dain Nilsson * pivman/__init__.py: Bumped version to 0.3.0 after rename. 2015-04-09 Dain Nilsson * .gitignore, README, pivman/__init__.py, pivman/controller.py, pivman/libloader.py, pivman/libykpiv.py, pivman/main.py, pivman/messages.py, pivman/piv.py, pivman/piv_cmd.py, pivman/storage.py, pivman/utils.py, pivman/view/__init__.py, pivman/view/cert.py, pivman/view/generate_dialog.py, pivman/view/init_dialog.py, pivman/view/main.py, pivman/view/manage.py, pivman/view/set_key_dialog.py, pivman/view/set_pin_dialog.py, pivman/view/settings_dialog.py, pivman/view/utils.py, pivman/watcher.py, pivman/worker.py, pivtool/__init__.py, pivtool/controller.py, pivtool/libloader.py, pivtool/libykpiv.py, pivtool/main.py, pivtool/messages.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/storage.py, pivtool/utils.py, pivtool/view/__init__.py, pivtool/view/cert.py, pivtool/view/generate_dialog.py, pivtool/view/init_dialog.py, pivtool/view/main.py, pivtool/view/manage.py, pivtool/view/set_key_dialog.py, pivtool/view/set_pin_dialog.py, pivtool/view/settings_dialog.py, pivtool/view/utils.py, pivtool/watcher.py, pivtool/worker.py, qt_resources.py, resources/pivman.desktop, resources/pivman.nsi, resources/pivman.spec, resources/pivtool.desktop, resources/pivtool.nsi, resources/pivtool.spec, scripts/pivman, scripts/pivtool, setup.py: Rename of project. 2015-04-09 Dain Nilsson * pivtool/piv.py, pivtool/piv_cmd.py: Only try to set CHUID on init and authentication. 2015-04-08 Dain Nilsson * README, pivtool/model/__init__.py, resources/pivtool.desktop, setup.py: Updated packaging. 2015-04-08 Dain Nilsson * pivtool/view/cert.py, pivtool/view/main.py, pivtool/view/manage.py: Better window sizing for other OSes. 2015-04-08 Dain Nilsson * pivtool/piv_cmd.py, resources/pivtool.spec: Search path and base directory for cli. 2015-04-07 Dain Nilsson * pivtool/controller.py, pivtool/piv.py, pivtool/view/main.py: Use read_version for poll instead of read CHUID. 2015-04-01 Dain Nilsson * pivtool/controller.py, pivtool/view/cert.py, pivtool/view/utils.py: Remove help button from input dialogs. 2015-04-01 Dain Nilsson * pivtool/view/cert.py, pivtool/view/generate_dialog.py, pivtool/view/init_dialog.py, pivtool/view/manage.py, pivtool/view/set_key_dialog.py, pivtool/view/set_pin_dialog.py, pivtool/view/settings_dialog.py, pivtool/view/utils.py: Use custom subclass of QDialog. 2015-04-01 Dain Nilsson * pivtool/storage.py: Remove unused settings. 2015-03-31 Dain Nilsson * pivtool/view/generate_dialog.py: Fix locked algorithm. 2015-03-31 Dain Nilsson * pivtool/storage.py, pivtool/view/generate_dialog.py: Use list of out formats instead of individual switches. 2015-03-31 Dain Nilsson * pivtool/controller.py, pivtool/view/cert.py, pivtool/view/generate_dialog.py, pivtool/view/init_dialog.py, pivtool/view/set_pin_dialog.py, pivtool/view/settings_dialog.py, pivtool/watcher.py: Use settings[] instead of settings.get() 2015-03-31 Dain Nilsson * pivtool/storage.py, pivtool/view/init_dialog.py, pivtool/view/manage.py: Remember pin_as_key choice for initialization. 2015-03-31 Dain Nilsson * pivtool/messages.py, pivtool/storage.py, pivtool/view/cert.py, pivtool/view/generate_dialog.py, pivtool/view/settings_dialog.py, pivtool/view/utils.py: Remember previous choices for generate key. 2015-03-31 Dain Nilsson * pivtool/view/manage.py: Disable set_key button when PIN is key and PIN is blocked. 2015-03-31 Dain Nilsson * pivtool/messages.py, pivtool/view/generate_dialog.py: Warn if CA can't be reached. 2015-03-31 Dain Nilsson * pivtool/view/cert.py: Read files in binary mode. 2015-03-30 Dain Nilsson * pivtool/storage.py, pivtool/utils.py, pivtool/view/generate_dialog.py, pivtool/view/settings_dialog.py: Replace HAS_CA with has_ca(). 2015-03-30 Dain Nilsson * pivtool/storage.py, pivtool/view/generate_dialog.py: Added policy setting for subject. 2015-03-30 Dain Nilsson * pivtool/storage.py, pivtool/view/cert.py: Added setting to disallow "Import from file..." 2015-03-30 Dain Nilsson * pivtool/messages.py, pivtool/storage.py, pivtool/view/generate_dialog.py: Added policy settings for out format. 2015-03-30 Dain Nilsson * pivtool/main.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/storage.py, pivtool/view/cert.py, pivtool/view/settings_dialog.py, pivtool/view/utils.py, pivtool/watcher.py: pyformat. 2015-03-30 Dain Nilsson * pivtool/main.py, pivtool/messages.py, pivtool/storage.py, pivtool/view/cert.py, pivtool/view/generate_dialog.py, pivtool/view/init_dialog.py, pivtool/view/main.py, pivtool/view/manage.py, pivtool/view/settings_dialog.py, pivtool/view/utils.py: UI tweaks. 2015-03-30 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/cert.py, pivtool/view/main.py, pivtool/view/manage.py, pivtool/watcher.py: Complete device reset. 2015-03-30 Dain Nilsson * pivtool/storage.py: Fix reading boolean settings. 2015-03-28 Dain Nilsson * pivtool/storage.py, pivtool/view/generate_dialog.py, pivtool/view/manage.py: Add setting for force algorithm. 2015-03-28 Dain Nilsson * pivtool/controller.py, pivtool/piv.py: Detect PUK blocked. 2015-03-28 Dain Nilsson * pivtool/controller.py, pivtool/storage.py, pivtool/view/cert.py, pivtool/view/init_dialog.py, pivtool/view/manage.py, pivtool/view/set_pin_dialog.py, pivtool/view/settings_dialog.py: Add defaults to settings, and setting for shown certs. 2015-03-28 Dain Nilsson * pivtool/messages.py, pivtool/view/generate_dialog.py: Remove Public Key as output form. 2015-03-27 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/view/generate_dialog.py, pivtool/view/main.py, pivtool/view/manage.py, pivtool/view/set_pin_dialog.py: Adding Reset PIN and Reset Device. 2015-03-27 Dain Nilsson * pivtool/view/set_pin_dialog.py: Fix problem when changing PIN with separate Key. 2015-03-26 Dain Nilsson * pivtool/view/generate_dialog.py: Create elements even when not displaying them. 2015-03-26 Dain Nilsson * pivtool/messages.py, pivtool/piv.py, pivtool/utils.py, pivtool/view/cert.py, pivtool/view/manage.py, pivtool/view/set_pin_dialog.py: More code cleanups. 2015-03-26 Dain Nilsson * pivtool/controller.py, pivtool/main.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/utils.py, pivtool/view/cert.py, pivtool/view/generate_dialog.py, pivtool/view/main.py, pivtool/view/set_key_dialog.py: Formatting cleanups. 2015-03-26 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/utils.py, pivtool/view/cert.py, pivtool/view/generate_dialog.py: Rewrote key generation. 2015-03-25 Dain Nilsson * pivtool/controller.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/view/cert.py, pivtool/view/main.py: Read slot status from CLI. 2015-03-25 Dain Nilsson * pivtool/controller.py: Fix incorrect call to generate_key. 2015-03-24 Dain Nilsson * pivtool/view/set_key_dialog.py: Better PIN prompting. 2015-03-24 Dain Nilsson * pivtool/controller.py, pivtool/view/main.py, pivtool/view/set_pin_dialog.py: Better handling of wrong PIN. 2015-03-24 Dain Nilsson * pivtool/controller.py, pivtool/piv.py, pivtool/view/cert.py, pivtool/view/utils.py, pivtool/worker.py: Verify and fail fast on PIN entry. 2015-03-23 Dain Nilsson * pivtool/worker.py: Removed help button from busy dialog. 2015-03-23 Dain Nilsson * pivtool/view/cert.py, pivtool/view/init_dialog.py, pivtool/view/manage.py, pivtool/view/set_key_dialog.py, pivtool/view/set_pin_dialog.py, pivtool/view/settings_dialog.py: Remove help button from dialogs. 2015-03-23 Dain Nilsson * pivtool/utils.py: Fix HAS_CA again. 2015-03-23 Dain Nilsson * pivtool/utils.py, pivtool/view/cert.py, pivtool/view/settings_dialog.py: Rename HAS_AD -> HAS_CA 2015-03-23 Dain Nilsson * pivtool/messages.py, pivtool/view/cert.py: Add descriptions to empty PIV slots. 2015-03-23 Dain Nilsson * pivtool/utils.py: Use certutil -dump for CA detection. 2015-03-23 Dain Nilsson * pivtool/view/main.py, pivtool/worker.py: Release references to callbacks after invocation. 2015-03-20 Dain Nilsson * pivtool/controller.py, pivtool/utils.py, pivtool/view/cert.py, pivtool/view/init_dialog.py, pivtool/view/main.py, pivtool/view/manage.py, pivtool/view/set_key_dialog.py, pivtool/view/set_pin_dialog.py, pivtool/watcher.py, pivtool/worker.py: Better locking and polling of device. 2015-03-18 Dain Nilsson * pivtool/controller.py: Invalidate PUK if not given during init. 2015-03-18 Dain Nilsson * pivtool/main.py, pivtool/messages.py, pivtool/view/cert.py: Fix messages. 2015-03-18 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/cert.py: Generate key. 2015-03-18 Dain Nilsson * pivtool/libykpiv.py, pivtool/piv.py: Fix some lib issues. 2015-03-17 Dain Nilsson * pivtool/view/main.py: Close application if PIN expired and not changed. 2015-03-17 Dain Nilsson * pivtool/messages.py, pivtool/view/set_key_dialog.py, pivtool/view/settings_dialog.py: Warn when blocking PUK. 2015-03-17 Dain Nilsson * pivtool/messages.py, pivtool/view/main.py, pivtool/view/manage.py: Handle blocked PUK. 2015-03-17 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/cert.py, pivtool/view/init_dialog.py, pivtool/view/main.py, pivtool/view/manage.py, pivtool/view/set_key_dialog.py, pivtool/view/set_pin_dialog.py: Added key management. 2015-03-17 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/view/cert.py: Improved file import. 2015-03-17 Dain Nilsson * pivtool/view/main.py: Use locking when refreshing the controller. 2015-03-16 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/view/cert.py: Support more file import formats. 2015-03-16 Dain Nilsson * pivtool/view/cert.py: Show expired dates in red. 2015-03-16 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/view/cert.py: Import from PFX. 2015-03-16 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/view/cert.py, pivtool/view/status.py: Delete certificates. 2015-03-16 Dain Nilsson * pivtool/messages.py, pivtool/view/cert.py: Export certificate. 2015-03-16 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/cert.py: Display installed certificates. 2015-03-13 Dain Nilsson * pivtool/messages.py, pivtool/view/cert.py: Added Certificates placeholder. 2015-03-13 Dain Nilsson * pivtool/view/cert.py, pivtool/view/main.py, pivtool/view/manage.py: Set size with single command. 2015-03-13 Dain Nilsson * pivtool/__init__.py, pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/cert.py, pivtool/view/main.py, pivtool/view/manage.py, pivtool/view/status.py: Replaced StatusDialog with MainWidget. 2015-03-13 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/settings_dialog.py, pivtool/view/status.py: Added settings for PIN expiration. 2015-03-12 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/status.py: Display number of days until PIN expires. 2015-03-12 Dain Nilsson * pivtool/controller.py, pivtool/storage.py, pivtool/view/set_pin_dialog.py: Only store PIN timestamp if expiration is enforced. 2015-03-12 Dain Nilsson * pivtool/controller.py, pivtool/view/status.py: Use QSslCertificate instead of pyasn1. 2015-03-10 Dain Nilsson * pivtool/utils.py, pivtool/view/settings_dialog.py, pivtool/view/status.py: Check if AD is available for AD specific options. 2015-03-10 Dain Nilsson * pivtool/messages.py, pivtool/storage.py, pivtool/view/settings_dialog.py, pivtool/view/status.py: Added setting for CertificateTemplate. 2015-03-10 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/storage.py, pivtool/view/init_dialog.py, pivtool/view/main.py, pivtool/view/set_pin_dialog.py, pivtool/view/settings_dialog.py, pivtool/view/utils.py: Added settings. 2015-03-09 Dain Nilsson * pivtool/storage.py: Add read-only overlay to settings. 2015-03-09 Dain Nilsson * pivtool/controller.py, pivtool/storage.py, pivtool/view/main.py: Improve settings. 2015-03-09 Dain Nilsson * pivtool/controller.py, pivtool/storage.py, pivtool/view/main.py: Wrap QSettings in more Pythonic class. 2015-03-06 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/init_dialog.py, pivtool/view/utils.py: Remove password and overhaul init dialog. 2015-03-05 Dain Nilsson * pivtool/messages.py, pivtool/view/init_dialog.py, pivtool/view/main.py: Validate init dialog data. 2015-03-05 Dain Nilsson * pivtool/view/set_pin_dialog.py: Don't prompt for PIN twice on set PIN. 2015-03-05 Dain Nilsson * pivtool/controller.py: Fix legacy data. 2015-03-05 Dain Nilsson * pivtool/__init__.py, pivtool/controller.py, pivtool/messages.py, pivtool/view/main.py: Poll for device automatically. 2015-03-04 Dain Nilsson * pivtool/controller.py: Migrate data saved using older version of app. 2015-03-04 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/init_dialog.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py, pivtool/view/utils.py, pivtool/worker.py: Prompt for management key on main thread. 2015-03-04 Dain Nilsson * pivtool/controller.py: Better text for management password prompt. 2015-03-04 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/init_dialog.py: Passphrase -> Password. 2015-03-04 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/init_dialog.py: Better authentication logic. 2015-03-03 Dain Nilsson * pivtool/controller.py: Prompt for passphrase/key when needed. 2015-03-03 Dain Nilsson * pivtool/controller.py, pivtool/view/main.py: Add window to controller. 2015-03-03 Dain Nilsson * pivtool/messages.py, pivtool/view/init_dialog.py, pivtool/view/initialize.py, pivtool/view/main.py, pivtool/view/settings_dialog.py: New init dialog. 2015-03-04 Dain Nilsson * pivtool/controller.py: Avoid writing data when unchanged. 2015-03-03 Dain Nilsson * pivtool/view/set_pin_dialog.py: Fix message for non-forced pin dialog. 2015-03-03 Dain Nilsson * pivtool/controller.py, pivtool/libykpiv.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/utils.py, pivtool/view/status.py: Use single ojbect for pivtool data. 2015-03-02 Dain Nilsson * pivtool/controller.py, pivtool/piv.py, pivtool/utils.py: Improved cert reading. 2015-02-27 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/status.py: Move properties to controller. Prompt for CertificateTemplate. 2015-02-27 Dain Nilsson * : Added app icon. 2015-02-26 Dain Nilsson * pivtool/controller.py: Better error for failed certreq call. 2015-02-26 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/initialize.py, pivtool/view/set_pin_dialog.py: Cleanups. 2015-02-26 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/initialize.py, pivtool/view/main.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py, pivtool/worker.py: Better handling of errors. 2015-02-26 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/status.py: Better message for no cert loaded. 2015-02-25 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, resources/pivtool.nsi: Do a PIN expiry check on startup. 2015-02-25 Dain Nilsson * pivtool/controller.py: certreq needs to display prompt. 2015-02-25 Dain Nilsson * pivtool/controller.py, pivtool/piv_cmd.py: Don't show console for external commands. 2015-02-25 Dain Nilsson * pivtool/controller.py, pivtool/utils.py: test utility function. 2015-02-25 Dain Nilsson * pivtool/controller.py: Save the certificate to a non-existing file. 2015-02-25 Dain Nilsson * pivtool/controller.py, pivtool/main.py, pivtool/piv.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py: Read expiration of certificates. 2015-02-24 Dain Nilsson * pivtool/main.py: Add explicit imports for Pyinstaller to work. 2015-02-24 Dain Nilsson * pivtool/main.py: Improved PIN expiry check. 2015-02-24 Dain Nilsson * pivtool/main.py, pivtool/view/main.py: Add -c for PIN expiration check only. 2015-02-24 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/view/main.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py: Force PIN change if expired. 2015-02-24 Dain Nilsson * pivtool/controller.py, pivtool/piv.py, pivtool/view/initialize.py: Invalidate PUK on initialization. 2015-02-24 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/piv.py, pivtool/utils.py, pivtool/view/initialize.py, pivtool/view/main.py, pivtool/view/set_pin_dialog.py: Initialize devices with default authentication key. 2015-02-23 Dain Nilsson * pivtool/messages.py, pivtool/utils.py, pivtool/view/status.py: Add warning before requesting certificate. 2015-02-23 Dain Nilsson * pivtool/messages.py, pivtool/utils.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py, pivtool/worker.py: Add pin complexity. 2015-02-23 Dain Nilsson * pivtool/controller.py, pivtool/messages.py, pivtool/model/controller.py, pivtool/view/main.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py, pivtool/worker.py: More stuff. 2015-02-21 Dain Nilsson * pivtool/main.py, pivtool/messages.py, pivtool/piv.py, pivtool/view/status.py, pivtool/worker.py: Added Worker for background tasks. 2015-02-20 Dain Nilsson * pivtool/model/controller.py, pivtool/utils.py, pivtool/view/set_pin_dialog.py: Store salt in applet. 2015-02-20 Dain Nilsson * pivtool/model/controller.py, pivtool/piv.py, pivtool/utils.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py: Handle unicode PINs. 2015-02-20 Dain Nilsson * pivtool/model/__init__.py, pivtool/model/controller.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/utils.py, pivtool/view/main.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py: Derive management key from PIN. 2015-02-19 Dain Nilsson * pivtool/messages.py, pivtool/piv.py, pivtool/view/set_pin_dialog.py, pivtool/view/status.py, resources/pivtool.spec: Add Change PIN dialog. 2015-02-19 Dain Nilsson * resources/pivtool.spec: Allow .exes in lib/ 2015-02-19 Dain Nilsson * pivtool/libykpiv.py, resources/pivtool.nsi: Fixes. 2015-02-18 Dain Nilsson * MANIFEST.in: Added manifest. 2015-02-18 Dain Nilsson * resources/pivtool.nsi, resources/pivtool.spec, resources/qt.conf: Added installer stuff. 2015-02-18 Dain Nilsson * pivtool/messages.py, pivtool/piv.py, pivtool/view/main.py, pivtool/view/status.py: Added some UI. 2015-02-18 Dain Nilsson * pivtool/libykpiv.py, pivtool/piv.py, pivtool/piv_cmd.py, pivtool/ykpiv.py, scripts/pivtool: More features. 2015-02-17 Dain Nilsson * pivtool/piv.py, pivtool/piv_cmd.py, pivtool/ykpiv.py: Added stuff. 2015-02-17 Dain Nilsson * pivtool/libloader.py, pivtool/ykpiv.py: Added ykpiv ctypes. 2015-02-13 Dain Nilsson * pivtool/view/main.py: Give window min width/height. 2015-02-13 Dain Nilsson * Initial skeleton. yubikey-piv-manager-1.3.0/COPYING0000644000076500000240000010451312752100505016312 0ustar dagstaff00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . yubikey-piv-manager-1.3.0/doc/0000755000076500000240000000000012755276415016041 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/doc/Certificates.adoc0000644000076500000240000000546312752100505021265 0ustar dagstaff00000000000000== Certificates The PIV functionality of the YubiKey provides 4 standard slots for storing private keys with accompanying X509 certificates. You can view and manage these slots from the *Certificates* dialog. For more information on the different slots and their use, see the link:http://csrc.nist.gov/groups/SNS/piv/standards.html[PIV standards documents]. === Generating a new key pair The first step is to generate a new cryptographic key pair. The *Certificates* dialog provides a *Generate new key...* button to start this process. Each slot is represented as a tab in the dialog, and each tab has its own button to generate a key. You will need to specify the algorithm for the key, and the output format. The private key will be generated on the YubiKey, and will never leave the device. What happens to the public key is determined by the output type. ==== Output: Public key The most basic output type is to just write the public key to the disk, in either PEM or DER format. NOTE: This output form is hidden by default, as it is of no use for most users. ==== Output: Self-signed certificate The public key is wrapped in an X509 certificate, which is then self-signed by the private key, and stored in the same slot as the private key of the YubiKey. You will need to provide a Subject DN for the certificate to use, in the following format: .... /CN=host.example.com/OU=test/O=example.com .... ==== Output: Certificate signing request (CSR) The public key is wrapped in a CSR, which can be signed by a Certificate Authority (CA), resulting in a CA-signed certificate. As with the previous output option, you will need to provide a Subject DN. The CSR is sent to the CA out-of-band, and the signed certificate should be imported into the YubiKey using the *Import from file...* button of the *Certificate* window, for the same slot that already holds the key. NOTE: If a private key but no certificate is loaded in a slot in the YubiKey, it will be indistinguishable from an empty slot. ==== Output: Request a certificate If the client is connected to a CA, the process of having the CA create and sign a certificate can be automated by YubiKey PIV Manager. This option will create a CSR, have the CA sign it, and import it back into the YubiKey. As with the previous output option, you will need to provide a Subject DN. NOTE: This feature is only available on Windows, and uses the *certreq* executable. === Importing a private key/certificate Instead of generating a key pair on the YubiKey itself, you can import an existing private key and/or certificate. To do so simply use the *Import from file...* button in the *Certificates* dialog. The YubiKey PIV Manager supports importing private keys in PEM and PFX format and certificates in DER, PEM and PFX format. NOTE: There is no way to see that a private key has been imported into a slot. yubikey-piv-manager-1.3.0/doc/development.adoc0000644000076500000240000000441612753077700021212 0ustar dagstaff00000000000000== Development === Dependencies YubiKey PIV Manager requires PySide, pycrypto and yubico-piv-tool. === Building binaries Binaries for Windows and OSX are built using PyInstaller. Get the source release file, yubikey-piv-manager-.tar.gz, and extract it. It should contain a single directory, henceforth refered to as the release directory. When building binaries for Windows or OS X, you will need to include .dll/.dylib files from the yubico-piv-tool project, as well as the yubico-piv-tool executable. Create a subdirectory called "lib" in the release directory. Download the correct binary release for your architecture from https://developers.yubico.com/yubico-piv-tool/Releases/ and extract the .dll/.dylib files and executable to the "lib" directory you created previously. ==== Windows For Windows you will need python, PySide, PyCrypto, PyInstaller and Pywin32 installed (32 or 64-bit versions depending on the architecture of the binary your are building). To sign the executable you will need signtool.exe (from the Windows SDK) either copied into the root as well or in a location in your PATH, as well as a certificate in the Windows certificate store that you wish to sign with. Run "python setup.py executable" from the main release directory. With NSIS installed, a Windows installer will be built as well. ==== OSX For OSX you need python, pyside, pycrypto, and pyinstaller installed. One way to install these dependencies is by using Homebrew: brew install python brew install pyside pip install PyInstaller pip install pycrypto NOTE: Homebrew will build backwards-incompatible binaries, so the resulting build will not run on an older version of OSX. Run "python setup.py executable" from the main release directory. This will create an .app in the dist directory. Sign the code using codesign: codesign -s 'Developer ID Application' dist/YubiKey\ PIV\ Manager.app --deep There is also a project file for use with http://s.sudre.free.fr/Packaging.html[Packages] located at `resources/pivman.pkgproj`. This can be used to create an installer for distribution, which you should sign prior to distribution: packagesbuild resources/osx-installer.pkgproj productsign --sign 'Developer ID Installer' dist/YubiKey\ PIV\ Manager.pkg dist/yubikey-piv-manager-mac.pkg yubikey-piv-manager-1.3.0/doc/Device_Setup.adoc0000644000076500000240000000235512752100505021234 0ustar dagstaff00000000000000== Device Setup The YubiKey PIV Manager provides a wizard for initializing an un-initialized YubiKey. This wizard assumes the default values for the PIN, PUK and Management Key are set, and may not work correctly if they have been modified. Some features of the YubiKey PIV Manager require that all management of the device be done using the YubiKey PIV Manager, so it is recommended that you not use other tools in combination with the YubiKey PIV Manager to manage your YubiKeys PIV configuration. === Prerequisites You will need to have the YubiKey PIV Manager and its dependencies installed, as well as a PIV-enabled YubiKey, with the default PIN, PUK and Management Key values set. === Device Initialization A PIV-enabled YubiKey comes pre-programmed with the default values for the PIN, the PUK, and the Management Key. As these default values are known it is crucial that these be changed before any real use. The YubiKey PIV Manager will automatically show a _Device Initialization_ dialog if it detects a YubiKey that uses the default Management Key. It will ask you to provide a PIN, Management Key, and optionally a PUK. For more details on the various options provided by this wizard, see link:PIN_and_Management_Key.adoc[PIN and Management Key]. yubikey-piv-manager-1.3.0/doc/PIN_and_Management_Key.adoc0000644000076500000240000001227212752100505023070 0ustar dagstaff00000000000000== PIN and Management Key A PIV-enabled YubiKey has a PIN, a PUK and a Management Key. These can be configured by using the *Manage Device PINs* window of the YubiKey PIV Manager. === PIN The PIN is used during normal operation to authorize an action such as creating a digital signature for any of the loaded certificates. Entering an incorrect PIN three times consecutively will cause the PIN to become blocked, rendering the PIV features unusable. The PIN must be at least 4 characters, and can contain any characters, though it is recommended to only use those contained in the ASCII character set. There is a limit of 8 bytes for a PIN, which allows for up to 8 ASCII characters. There is a setting in the YubiKey PIV Manager, which enforces password complexity rules on your PIN (as well as your PUK). If this setting is active, your PIN will need to: * Be at least 6 characters in length. * Not contain all or part of the user's account name. * Contain characters from three of the following four categories: ** English uppercase characters (A through Z) ** English lowercase characters (a through z) ** Base 10 digits (0 through 9) ** Nonalphanumeric characters (e.g., !, $, #, %) ==== PIN expiration There is an option in the YubiKey PIV Manager to enforce PINs to expire after a certain number of days, requiring you to change your PIN periodically. This is not enabled by default. When enabled, the YubiKey PIV Manager will prompt you to change the PIN after the set number of days has passed. Changing the PIN using an external tool will not affect the PIN expiration date. When this option is enabled you will need to provide your Management Key each time the PIN is changed, as that is required to store the updated expiration time. Because of this it is often desirable to use your PIN as the Management Key in combination with this setting to not be prompted for the Key. === Management Key All PIV management operation of the YubiKey require a 24 byte 3DES key, known as the Management Key. You can either explicitly set a 24 byte key (the YubiKey PIV Manager can generate one for you), or you can choose to not set a Management Key, instead using the PIN for these operations. If so, you will be asked to provide your PIN instead of your Management Key whenever you perform a management operation, such as importing a new certificate, or generating a new key pair. NOTE: See the section Considerations when using a PIN for PIV management! === PUK The PUK can be used to reset the PIN if it is ever lost or becomes blocked after the maximum number of incorrect attempts. Setting a PUK is optional. If you use your PIN as the Management Key, the PUK is disabled for technical reasons, explained in a later section. The requirements and restrictions of the PUK are the same as for the PIN (see above). If PIN complexity is enforced, the same rules are applied to the PUK. If the PUK ever becomes blocked, either by deliberately choosing to block it or by giving the wrong PUK value 3 times, it can only be unblocked by performing a complete reset (explained below). === Resetting a device If an incorrect PIN is given 3 times consecutively, the PIN will become disabled. If you've set a PUK, then you can use that PUK to reset the PIN to a new value, and it will become enabled and usable again. If an incorrect PUK is given 3 times consecutively, it will become blocked as well. When both the PIN and the PUK are blocked, the device can be reset. This returns the PIV functionality of the YubiKey to a factory setting, setting the default PIN, PUK and Management Key values, as well as removing any stored keys and certificates. Once reset, the device is ready to be re-initialized. === Considerations when using a PIN for PIV management There are certain security and usability considerations which should be taken into account when using the PIN for PIV management, instead of a Management Key. The way this feature works, is that a Management Key is still used, but it is cryptographically derived from your PIN by the YubiKey PIV Manager, behind the scenes. One implication of this is that the Management Key changes whenever you change your PIN, and it is therefore crucial that you ONLY change your PIN using the YubiKey PIN Manager. Changing it using an external tool will render the YubiKey PIV Manager unable to derive the Management Key. There is also a security aspect to be aware of, and that is that even though the PIN is blocked if entered incorrectly 3 times, the Management Key is not. An attacker could use the knowledge of this to effectively brute-force the PIN. This is mitigated by a computationally expensive (slow) key derivation, but it should only be used with a long, complex PIN. ==== Technical description of Key derivation from PIN When choosing to use a Management Key derived from the PIN, the following takes place: 1. A random 8-byte SALT value is generated and stored on the YubiKey. 2. The derived Management Key is calculated as PBKDF2(PIN, SALT, 24, 10000). The PBKDF2 function (described in RFC 2898) is run using the PIN (encoded using UTF-8) as the password, for 10000 rounds, to produce a 24 byte key, which is used as the management key. Whenever the user changes the PIN this process is repeated, using a new SALT and the new PIN. yubikey-piv-manager-1.3.0/doc/Settings_and_Group_Policy.adoc0000644000076500000240000001016212752100505023765 0ustar dagstaff00000000000000== Settings and Group Policy The YubiKey PIV Manager provides various settings which can be used to customize its behavior. Some of these can be changed in the *Settings* dialog from within the tool. Each of these can be enforced using a Group Policy on Windows, which prevents the user from changing them. To do so you will need to set certain registry keys. This can be used to simplify the tool by limiting it to a subset of its functionality, and to enforce company policy on usage. === User settings User settings are stored in a file named pivman.ini, located in the .pivman subdirectory of the users come directory. For example: .... /home/username/.pivman/pivman.ini .... on Linux, or: .... c:\Users\username\.pivman\pivman.ini .... on Windows. Some of these values are changed by normal use of the YubiKey PIV Manager by performing actions in the tool. Others are only available by manually editing the configuration file. === Group Policy settings All user settings are available on Group Policy level, and take precedence over those on user level. On Windows these settings are stored as registry keys, under either: .... Computer\HKEY_CURRENT_USER\Software\Yubico\YubiKey PIV Manager .... or: .... Computer\HKEY_LOCAL_MACHINE\Software\Yubico\YubiKey PIV Manager .... === Available settings ==== Algorithm Which algorithm to use for key pair generation. key:: algorithm type:: string registry key type:: REG_SZ valid options:: "RSA1024", "RSA2048", "ECC256", "ECC384" default value:: "RSA2048" ==== Card Reader String to match against when looking for compatible YubiKey devices. key:: card_reader type:: string registry key type:: REG_SZ default value:: None ==== Certreq Template Value to use in CertificateTemplate parameter when calling certreq.exe. key:: certreq_template type:: string registry key type:: REG_SZ default value:: None ==== Complex PIN/PUKs True to require complex PINs and PUKs. key:: complex_pins type:: string registry key type:: REG_SZ default value:: "false" ==== Enable Import When False, hide the "import from file..." button for certificates. key:: enable_import type:: string registry key type:: REG_SZ default value:: "true" ==== PIN as Management Key When true, the Management Key is based off of the PIN. key:: pin_as_key type:: bool registry key type:: REG_SZ default value:: "false" ==== PIN Expiration When non-zero causes a timestamp to be written when the PIN is changed, and to force a PIN change after the specified number of days. key:: pin_expiration type:: int registry key type:: REG_DWORD default value:: 0 ==== PIN Requirement Policy When set to a value other than "default", override the PIV standard for when the PIN is required for using a particular slot. key:: pin_policy type:: string registry key type:: REG_SZ valid options:: "default" "never", "once", "always" default value:: "default" ==== PIN Policy Slots Which certificate slots to show the PIN Requirement Policy setting for. key:: pin_policy_slots type:: list of strings registry key type:: REG_MULTI_SZ valid options:: "9a", "9c", "9d", "9e" default value:: [] ==== Displayed Output Formats Output formats available when generating a key. key:: shown_outs type:: list of strings registry key type:: REG_MULTI_SZ valid options:: "pk", "ssc", "csr", "ca" default value:: ["ssc", "csr", "ca"] ==== Displayed Certificate Slots A list of which certificate slots to show in the UI. key:: shown_slots type:: list of strings registry key type:: REG_MULTI_SZ valid options:: "9a", "9c", "9d", "9e" default value:: ["9a", "9c", "9d", "9e"] ==== Subject DN Subject to use when generating a CSR or self-signed certificate. key:: subject type:: string registry key type:: REG_SZ default value:: "/CN=%USERNAME%" ==== Touch Policy When enabled, the YubiKey will require its button to be touched to perform any action with the private key of a slot. key:: touch_policy type:: bool registry key type:: REG_SZ default value:: "false" ==== Touch Policy Slots Which certificate slots to show the Touch Policy setting for. key:: touch_policy_slots type:: list of strings registry key type:: REG_MULTI_SZ valid options:: "9a", "9c", "9d", "9e" default value:: [] yubikey-piv-manager-1.3.0/man/0000755000076500000240000000000012755276415016047 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/man/pivman.10000644000076500000240000000335612752100505017411 0ustar dagstaff00000000000000.\" Copyright (c) 2014 Yubico AB .\" All rights reserved. .\" .\" This program is free software: you can redistribute it and/or modify .\" it under the terms of the GNU General Public License as published by .\" the Free Software Foundation, either version 3 of the License, or .\" (at your option) any later version. .\" .\" This program is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public License .\" along with this program. If not, see . .\" .\" Additional permission under GNU GPL version 3 section 7 .\" .\" If you modify this program, or any covered work, by linking or .\" combining it with the OpenSSL project's OpenSSL library (or a .\" modified version of that library), containing parts covered by the .\" terms of the OpenSSL or SSLeay licenses, We grant you additional .\" permission to convey the resulting work. Corresponding Source for a .\" non-source form of such a combination shall include the source code .\" for the parts of OpenSSL used as well as that of the covered work. .\" .\" The following commands are required for all man pages. .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH pivman "1" "Mar 2014" "yubikey-piv-manager" .SH NAME pivman - Tool for configuring your PIV-enabled YubiKey. .SH SYNOPSIS .B pivman .SH DESCRIPTION YubiKey PIV Manager is a graphical utility to manage keys and certificates stored in a PIV-enabled YubiKey.. .SH BUGS Report enroll bugs in .URL "https://github.com/Yubico/yubikey-piv-manager/issues" "the issue tracker" yubikey-piv-manager-1.3.0/MANIFEST.in0000644000076500000240000000022212752100505017005 0ustar dagstaff00000000000000include COPYING include NEWS include ChangeLog include screenshot.png include resources/* include qt_resources/* include doc/*.adoc include man/* yubikey-piv-manager-1.3.0/NEWS0000644000076500000240000000266012755275402015772 0ustar dagstaff00000000000000* Version 1.3.0 (released 2016-08-18) ** Updated to be inline with yubico-piv-tool 1.4.2. ** Allow generation of a authentication certificate when initialising a new YubiKey. ** Set the CCC when initialising a new YubiKey. ** Show an option for expiration date when generating new certificates. ** Add support for importing PEM files that contains both a private key and a certificate. ** Bugfix: Connecting to a Certificate Authority on a localised Windows environment should now work. ** Add compability with Python 3. * Version 1.2.1 (released 2015-12-08) ** Bugfix: The device initialization dialog was not being shown. * Version 1.2.0 (released 2015-12-07) ** Packaging improvements. ** Don't expose some advanced functions (certificate PIN/touch policy) by default, as they are likely to cause confusion. * Version 1.1.1 (released 2015-11-16) ** Better handling of intermittent device disconnects. * Version 1.1.0 (released 2015-11-12) ** Added support for ECC P-384 when supported by the device. ** Added usage policy when supported by the device. ** Update to libykpiv1. * Version 1.0.2 (released 2015-05-18) ** Fix bug preventing device from being found with multiple card readers attached. ** Don't save window position as it causes problems with multi-monitor setups. * Version 1.0.1 (released 2015-04-16) ** Packaging improvements release, no new features. * Version 1.0.0 (released 2015-04-14) ** Initial version. yubikey-piv-manager-1.3.0/pivman/0000755000076500000240000000000012755276415016566 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/pivman/__init__.py0000644000076500000240000000233712755275320020676 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. __version__ = "1.3.0" yubikey-piv-manager-1.3.0/pivman/__main__.py0000644000076500000240000001102212754343340020643 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from __future__ import print_function import sys import argparse import signal import pivman.qt_resources # noqa: F401 from PySide import QtGui, QtCore from pivman.view.main import MainWidget from pivman import __version__ as version, messages as m from pivman.piv import YkPiv, libversion as ykpiv_version from pivman.controller import Controller from pivman.view.set_pin_dialog import SetPinDialog from pivman.view.settings_dialog import SettingsDialog from pivman.yubicommon import qt ABOUT_TEXT = """

%s

%s
%s

%s

%%s

""" % (m.app_name, m.copyright, m.version_1, m.libraries) class PivtoolApplication(qt.Application): def __init__(self, argv): super(PivtoolApplication, self).__init__(m, version) QtCore.QCoreApplication.setOrganizationName(m.organization) QtCore.QCoreApplication.setOrganizationDomain(m.domain) QtCore.QCoreApplication.setApplicationName(m.app_name) args = self._parse_args() if args.check_only: self.check_pin() self.quit() return self.ensure_singleton() self._build_menu_bar() self._init_window() def check_pin(self): try: controller = Controller(YkPiv()) if controller.is_uninitialized(): print('Device not initialized') elif controller.is_pin_expired(): dialog = SetPinDialog(controller, None, True) if dialog.exec_(): QtGui.QMessageBox.information(None, m.pin_changed, m.pin_changed_desc) except: print('No YubiKey PIV applet detected') def _parse_args(self): parser = argparse.ArgumentParser(description='YubiKey PIV Manager', add_help=True) parser.add_argument('-c', '--check-only', action='store_true') return parser.parse_args() def _init_window(self): self.window.setWindowTitle(m.win_title_1 % self.version) self.window.setWindowIcon(QtGui.QIcon(':/pivman.png')) self.window.layout().setSizeConstraint(QtGui.QLayout.SetFixedSize) self.window.setCentralWidget(MainWidget()) self.window.show() self.window.raise_() def _build_menu_bar(self): file_menu = self.window.menuBar().addMenu(m.menu_file) settings_action = QtGui.QAction(m.action_settings, file_menu) settings_action.triggered.connect(self._show_settings) file_menu.addAction(settings_action) help_menu = self.window.menuBar().addMenu(m.menu_help) about_action = QtGui.QAction(m.action_about, help_menu) about_action.triggered.connect(self._about) help_menu.addAction(about_action) def _libversions(self): return 'ykpiv: %s' % ykpiv_version.decode('ascii') def _about(self): QtGui.QMessageBox.about( self.window, m.about_1 % m.app_name, ABOUT_TEXT % (self.version, self._libversions()) ) def _show_settings(self): dialog = SettingsDialog(self.window) if dialog.exec_(): self.window.centralWidget().refresh() def main(): signal.signal(signal.SIGINT, signal.SIG_DFL) app = PivtoolApplication(sys.argv) sys.exit(app.exec_()) if __name__ == '__main__': main() yubikey-piv-manager-1.3.0/pivman/controller.py0000644000076500000240000003312412755260473021323 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from pivman.utils import test, der_read from pivman.piv import PivError, WrongPinError from pivman.storage import settings, SETTINGS from pivman.view.utils import get_active_window, get_text from pivman import messages as m from pivman.yubicommon.compat import text_type, byte2int, int2byte from PySide import QtGui, QtNetwork from datetime import timedelta from hashlib import pbkdf2_hmac from binascii import a2b_hex import os import re import time import struct YKPIV_OBJ_PIVMAN_DATA = 0x5fff00 TAG_PIVMAN_DATA = 0x80 # Wrapper for pivman data TAG_FLAGS_1 = 0x81 # Flags 1 TAG_SALT = 0x82 # Salt used for management key derivation TAG_PIN_TIMESTAMP = 0x83 # When the PIN was last changed FLAG1_PUK_BLOCKED = 0x01 # PUK is blocked AUTH_SLOT = '9a' DEFAULT_SUBJECT = "/CN=Yubico PIV Authentication" AUTH_CERT_VALID_DAYS = 10950 # 30 years def parse_pivtool_data(raw_data): rest, _ = der_read(raw_data, TAG_PIVMAN_DATA) data = {} while rest: t, v, rest = der_read(rest) data[t] = v return data def serialize_pivtool_data(data): # NOTE: Doesn't support values > 0x80 bytes. buf = b'' for k, v in sorted(data.items()): buf += int2byte(k) + int2byte(len(v)) + v return int2byte(TAG_PIVMAN_DATA) + int2byte(len(buf)) + buf def has_flag(data, flagkey, flagmask): flags = byte2int(data.get(flagkey, b'\0')[0]) return bool(flags & flagmask) def set_flag(data, flagkey, flagmask, value=True): flags = byte2int(data.get(flagkey, b'\0')[0]) if value: flags |= flagmask else: flags &= ~flagmask data[flagkey] = int2byte(flags) def derive_key(pin, salt): if pin is None: raise ValueError('PIN must not be None!') if isinstance(pin, text_type): pin = pin.encode('utf8') return pbkdf2_hmac('sha1', pin, salt, 10000, dklen=24) def is_hex_key(string): try: return bool(re.compile(r'^[a-fA-F0-9]{48}$').match(string)) except: return False class Controller(object): def __init__(self, key): self._key = key self._authenticated = False try: self._raw_data = self._key.fetch_object(YKPIV_OBJ_PIVMAN_DATA) # TODO: Remove in a few versions... if byte2int(self._raw_data[0]) != TAG_PIVMAN_DATA: self._data = {} self._data[TAG_PIN_TIMESTAMP] = self._raw_data self._data[TAG_SALT] = self._key.fetch_object( YKPIV_OBJ_PIVMAN_DATA + 1) else: # END legacy stuff self._data = parse_pivtool_data(self._raw_data) except PivError: self._raw_data = serialize_pivtool_data({}) self._data = {} def poll(self): return test(self._key._read_version) def reconnect(self): self._key.reconnect() def _save_data(self): raw_data = serialize_pivtool_data(self._data) if raw_data != self._raw_data: self.ensure_authenticated() self._key.save_object(YKPIV_OBJ_PIVMAN_DATA, raw_data) self._raw_data = raw_data @property def version(self): return self._key.version @property def version_tuple(self): return tuple(map(int, self.version.split(b'.'))) @property def authenticated(self): return self._authenticated @property def pin_is_key(self): return TAG_SALT in self._data @property def pin_blocked(self): return self._key.pin_blocked @property def puk_blocked(self): return has_flag(self._data, TAG_FLAGS_1, FLAG1_PUK_BLOCKED) def verify_pin(self, pin): if len(pin) > 8: raise ValueError('PIN must be no longer than 8 bytes!') self._key.verify_pin(pin) def ensure_pin(self, pin=None, window=None): if window is None: window = get_active_window() if pin is not None: try: self.verify_pin(pin) return pin except WrongPinError as e: if e.blocked: raise QtGui.QMessageBox.warning(window, m.error, str(e)) except ValueError as e: QtGui.QMessageBox.warning(window, m.error, str(e)) pin, status = get_text( window, m.enter_pin, m.pin_label, QtGui.QLineEdit.Password) if not status: raise ValueError('PIN entry aborted!') return self.ensure_pin(pin, window) def ensure_authenticated(self, key=None, window=None): if self.authenticated or test(self.authenticate, catches=ValueError): return if window is None: window = get_active_window() if self.pin_is_key: key = self.ensure_pin(key, window) self.authenticate(key) return elif key is not None: try: self.authenticate(key) return except ValueError: pass self._do_ensure_auth(None, window) def _do_ensure_auth(self, key, window): if key is not None: try: self.authenticate(key) return except ValueError as e: QtGui.QMessageBox.warning(window, m.error, str(e)) key, status = get_text(window, m.enter_key, m.key_label) if not status: raise ValueError('Key entry aborted!') self._do_ensure_auth(key, window) def reset_device(self): self._key.reset_device() def authenticate(self, key=None): salt = self._data.get(TAG_SALT) if key is not None and salt is not None: key = derive_key(key, salt) elif is_hex_key(key): key = a2b_hex(key) self._authenticated = False if test(self._key.authenticate, key, catches=PivError): self._authenticated = True else: raise ValueError(m.wrong_key) def is_uninitialized(self): return not self._data and test(self._key.authenticate) def _invalidate_puk(self): set_flag(self._data, TAG_FLAGS_1, FLAG1_PUK_BLOCKED) for i in range(8): # Invalidate the PUK test(self._key.set_puk, '', '000000', catches=ValueError) def initialize(self, auth_cert, pin, puk=None, key=None, old_pin='123456', old_puk='12345678'): if not self.authenticated: self.authenticate() if key is None: # Derive key from PIN self._data[TAG_SALT] = b'' # Used as a marker for change_pin else: self.set_authentication(key) if puk is None: self._invalidate_puk() else: self._key.set_puk(old_puk, puk) self.change_pin(old_pin, pin) if auth_cert: self.create_auth_cert(pin) def create_auth_cert(self, pin): generated_key = self.generate_key(AUTH_SLOT) cert = self.selfsign_certificate( AUTH_SLOT, pin, generated_key, DEFAULT_SUBJECT, AUTH_CERT_VALID_DAYS) self.import_certificate(cert, AUTH_SLOT) def set_authentication(self, new_key, is_pin=False): if not self.authenticated: raise ValueError('Not authenticated') if is_pin: self.verify_pin(new_key) salt = os.urandom(16) key = derive_key(new_key, salt) self._data[TAG_SALT] = salt self._key.set_authentication(key) # Make sure PUK is invalidated: if not has_flag(self._data, TAG_FLAGS_1, FLAG1_PUK_BLOCKED): self._invalidate_puk() else: if is_hex_key(new_key): new_key = a2b_hex(new_key) self._key.set_authentication(new_key) if self.pin_is_key: del self._data[TAG_SALT] self._save_data() def change_pin(self, old_pin, new_pin): if len(new_pin) < 6: raise ValueError('PIN must be at least 6 characters') self.verify_pin(old_pin) if self.pin_is_key or self.does_pin_expire(): self.ensure_authenticated(old_pin) self._key.set_pin(new_pin) # Update management key if needed: if self.pin_is_key: self.set_authentication(new_pin, True) if self.does_pin_expire(): self._data[TAG_PIN_TIMESTAMP] = struct.pack('i', int(time.time())) self._save_data() def reset_pin(self, puk, new_pin): if len(new_pin) < 6: raise ValueError('PIN must be at least 6 characters') try: self._key.reset_pin(puk, new_pin) except WrongPinError as e: if e.blocked: set_flag(self._data, TAG_FLAGS_1, FLAG1_PUK_BLOCKED) raise def change_puk(self, old_puk, new_puk): if self.puk_blocked: raise ValueError('PUK is disabled and cannot be changed') if len(new_puk) < 6: raise ValueError('PUK must be at least 6 characters') try: self._key.set_puk(old_puk, new_puk) except WrongPinError as e: if e.blocked: set_flag(self._data, TAG_FLAGS_1, FLAG1_PUK_BLOCKED) raise def update_chuid(self): if not self.authenticated: raise ValueError('Not authenticated') self._key.set_chuid() def generate_key(self, slot, algorithm='RSA2048', pin_policy=None, touch_policy=False): if not self.authenticated: raise ValueError('Not authenticated') if pin_policy == 'default': pin_policy = None if slot in self.certs: self.delete_certificate(slot) return self._key.generate(slot, algorithm, pin_policy, touch_policy) def create_csr(self, slot, pin, pubkey, subject): self.verify_pin(pin) if not self.authenticated: raise ValueError('Not authenticated') return self._key.create_csr(subject, pubkey, slot) def selfsign_certificate(self, slot, pin, pubkey, subject, valid_days=365): self.verify_pin(pin) if not self.authenticated: raise ValueError('Not authenticated') return self._key.create_selfsigned_cert( subject, pubkey, slot, valid_days) def does_pin_expire(self): return bool(settings[SETTINGS.PIN_EXPIRATION]) def get_pin_last_changed(self): data = self._data.get(TAG_PIN_TIMESTAMP) if data is not None: data = struct.unpack('i', data)[0] return data def get_pin_days_left(self): validity = settings[SETTINGS.PIN_EXPIRATION] if not validity: return -1 last_changed = self.get_pin_last_changed() if last_changed is None: return 0 time_passed = timedelta(seconds=time.time() - last_changed) time_left = timedelta(days=validity) - time_passed return max(time_left.days, 0) def is_pin_expired(self): if not self.does_pin_expire(): return False last_changed = self.get_pin_last_changed() if last_changed is None: return True delta = timedelta(seconds=time.time() - last_changed) return delta.days > 30 @property def certs(self): return self._key.certs def get_certificate(self, slot): data = self._key.read_cert(slot) if data is None: return None return QtNetwork.QSslCertificate.fromData(data, QtNetwork.QSsl.Der)[0] def import_key(self, data, slot, frmt='PEM', password=None, pin_policy=None, touch_policy=False): if not self.authenticated: raise ValueError('Not authenticated') if pin_policy == 'default': pin_policy = None self._key.import_key(data, slot, frmt, password, pin_policy, touch_policy) def import_certificate(self, cert, slot, frmt='PEM', password=None): if not self.authenticated: raise ValueError('Not authenticated') try: self._key.import_cert(cert, slot, frmt, password) except ValueError: if len(cert) > 2048 and self.version_tuple < (4, 2, 7): raise ValueError('Certificate is to large to fit in buffer.') else: raise self.update_chuid() def delete_certificate(self, slot): if not self.authenticated: raise ValueError('Not authenticated') self._key.delete_cert(slot) yubikey-piv-manager-1.3.0/pivman/libykpiv.py0000644000076500000240000000743412752102044020760 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from ctypes import (Structure, POINTER, c_int, c_ubyte, c_char_p, c_long, c_ulong, c_size_t) from pivman.yubicommon.ctypes import CLibrary ykpiv_state = type('ykpiv_state', (Structure,), {}) ykpiv_rc = c_int class YKPIV(object): OK = 0 MEMORY_ERROR = -1 PCSC_ERROR = -2 SIZE_ERROR = -3 APPLET_ERROR = -4 AUTHENTICATION_ERROR = -5 RANDOMNESS_ERROR = -6 GENERIC_ERROR = -7 KEY_ERROR = -8 PARSE_ERROR = -9 WRONG_PIN = -10 INVALID_OBJECT = -11 ALGORITHM_ERROR = -12 class OBJ(object): CAPABILITY = 0x5fc107 CHUID = 0x5fc102 AUTHENTICATION = 0x5fc105 # cert for 9a key FINGERPRINTS = 0x5fc103 SECURITY = 0x5fc106 FACIAL = 0x5fc108 PRINTED = 0x5fc109 SIGNATURE = 0x5fc10a # cert for 9c key KEY_MANAGEMENT = 0x5fc10b # cert for 9d key CARD_AUTH = 0x5fc101 # cert for 9e key DISCOVERY = 0x7e KEY_HISTORY = 0x5fc10c IRIS = 0x5fc121 class ALGO(object): TDEA = 0x03 RSA1024 = 0x06 RSA2048 = 0x07 ECCP256 = 0x11 ECCP384 = 0x14 class LibYkPiv(CLibrary): ykpiv_strerror = [ykpiv_rc], c_char_p ykpiv_strerror_name = [ykpiv_rc], c_char_p ykpiv_init = [POINTER(POINTER(ykpiv_state)), c_int], ykpiv_rc ykpiv_done = [POINTER(ykpiv_state)], ykpiv_rc ykpiv_connect = [POINTER(ykpiv_state), c_char_p], ykpiv_rc ykpiv_disconnect = [POINTER(ykpiv_state)], ykpiv_rc ykpiv_transfer_data = [POINTER(ykpiv_state), POINTER(c_ubyte), POINTER(c_ubyte), c_long, POINTER(c_ubyte), POINTER(c_ulong), POINTER(c_int)], ykpiv_rc ykpiv_authenticate = [POINTER(ykpiv_state), POINTER(c_ubyte)], ykpiv_rc ykpiv_set_mgmkey = [POINTER(ykpiv_state), POINTER(c_ubyte)], ykpiv_rc ykpiv_hex_decode = [c_char_p, c_size_t, POINTER(c_ubyte), POINTER(c_size_t) ], ykpiv_rc ykpiv_sign_data = [POINTER(ykpiv_state), POINTER(c_ubyte), c_size_t, POINTER(c_ubyte), POINTER(c_size_t), c_ubyte, c_ubyte ], ykpiv_rc ykpiv_get_version = [POINTER(ykpiv_state), c_char_p, c_size_t], ykpiv_rc ykpiv_verify = [POINTER(ykpiv_state), c_char_p, POINTER(c_int)], ykpiv_rc ykpiv_fetch_object = [POINTER(ykpiv_state), c_int, POINTER(c_ubyte), POINTER(c_ulong)], ykpiv_rc ykpiv_save_object = [POINTER(ykpiv_state), c_int, POINTER(c_ubyte), c_size_t], ykpiv_rc ykpiv_check_version = [c_char_p], c_char_p ykpiv = LibYkPiv('ykpiv', '1') yubikey-piv-manager-1.3.0/pivman/messages.py0000644000076500000240000002602312754337070020744 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. """ Strings for YubiKey PIV Manager. Note: String names must not start with underscore (_). """ organization = "Yubico" domain = "yubico.com" app_name = "YubiKey PIV Manager" win_title_1 = "YubiKey PIV Manager (%s)" about_1 = "About: %s" copyright = "Copyright © Yubico" libraries = "Library versions" version_1 = "Version: %s" menu_file = "&File" menu_help = "&Help" action_about = "&About" action_settings = "&Settings" settings = "Settings" general = "General" misc = "Miscellaneous" certificates = "Certificates" active_directory = "Active Directory" active_directory_desc = "The following options are used when requesting a " \ "certificate from the Windows CA" reader_name = "Card reader name" no = "no" ok = "OK" cancel = "Cancel" error = "Error" refresh = "Refresh" no_key = "No YubiKey found. Please insert a PIV enabled YubiKey..." key_with_applet_1 = "YubiKey present with applet version: %s." name = "Name" name_1 = "Name: %s" wait = "Please wait..." device_unplugged = "Unable to communicate with the device, has it been removed?" certs_loaded_1 = "You have %s certificate(s) loaded." change_name = "Change name" change_name_desc = "Change the name of the device." current_pin_label = "Current PIN:" current_puk_label = "Current PUK:" current_key_label = "Current Management Key:" new_pin_label = "New PIN (6-8 characters):" new_key_label = "New Management Key:" verify_pin_label = "Repeat new PIN:" pin = "PIN" pin_label = "PIN:" pin_days_left_1 = "PIN expires in %s days." puk = "PUK" puk_label = "PUK:" new_puk_label = "PUK (6-8 characters):" verify_puk_label = "Repeat PUK:" puk_confirm_mismatch = "PUKs don't match!" no_puk = "No PUK set" no_puk_warning = "If you do not set a PUK you will not be able to reset your " \ "PIN in case it is ever lost. Continue without setting a PUK?" puk_not_complex = "PUK doesn't meet complexity rules" initialize = "Device Initialization" key_type_pin = "PIN (same as above)" key_type_key = "Key" key_invalid = "Invalid management key" key_invalid_desc = "The key you have provided is invalid. It should contain " \ "exactly 48 hexadecimal characters." management_key = "Management key" key_type_label = "Key type:" key_label = "Management Key:" use_pin_as_key = "Use PIN as key" use_separate_key = "Use a separate key" randomize = "Randomize" copy_clipboard = "Copy to clipboard" change_pin = "Change PIN" reset_pin = "Reset PIN" reset_device = "Reset device" reset_device_warning = "This will erase all data including keys and " \ "certificates from the device. Your PIN, PUK and Management Key will be " \ "reset to the factory defaults." resetting_device = "Resetting device..." device_resetted = "Device reset complete" device_resetted_desc = "Your device has now been reset, and will require " \ "initialization." change_puk = "Change PUK" change_key = "Change Management Key" change_pin_desc = "Change your PIN" change_pin_forced_desc = "Your PIN has expired and must now be changed." changing_pin = "Setting PIN..." changing_puk = "Setting PUK..." changing_key = "Setting Management Key..." initializing = "Initializing..." pin_changed = "PIN changed" pin_changed_desc = "Your PIN has been successfully changed." puk_changed = "PUK changed" puk_changed_desc = "Your PUK has been successfully changed." key_changed = "Management key changed" key_changed_desc = "Your management key has been successfully changed." pin_not_changed = "PIN not changed" pin_not_changed_desc = "New PIN must be different from old PIN" puk_not_changed = "PUK not changed" puk_not_changed_desc = "New PUK must be different from old PUK" pin_puk_same = "PIN and PUK the same" pin_puk_same_desc = "PIN and PUK must be different" puk_blocked = "PUK is blocked." block_puk = "PUK will be blocked" block_puk_desc = "Using your PIN as Management Key will block your PUK. " \ "You will not be able to recover your PIN if it is lost. A blocked PUK " \ "cannot be unblocked, even by setting a new Management Key." pin_confirm_mismatch = "PINs don't match!" pin_empty = "PIN is empty" pin_not_complex = "PIN doesn't meet complexity rules" pin_complexity_desc = """Your PIN/PUK must: * Not contain all or part of the user's account name * Be at least six characters in length * Contain characters from three of the following four categories: * English uppercase characters (A through Z) * English lowercase characters (a through z) * Base 10 digits (0 through 9) * Nonalphanumeric characters (e.g., !, $, #, %) """ enter_pin = "Enter PIN" enter_key = "Enter management key" manage_pin = "Manage device PINs" pin_is_key = "PIN is management key." enter_file_password = "Enter password to unlock file." password_label = "Password:" unknown = "Unknown" change_cert = "Request certificate" change_cert_warning_1 = "This will generate a new private key and request a " \ "certificate from the Windows CA, overwriting any previously stored " \ "credential in slot '%s' of your YubiKey's PIV applet. This action " \ "cannot be undone." changing_cert = "Requesting certificate..." export_to_file = "Export certificate..." export_cert = "Export certificate" save_pk = "Save Public Key as..." save_csr = "Save Certificate Signing Request as..." generate_key = "Generate new key..." generate_key_warning_1 = "A new private key will be generated and stored in " \ "slot '%s'. Anything currently in the slot will be deleted. This action " \ "cannot be undone." generating_key = "Generating new key..." generated_key = "New key generated" generated_key_desc_1 = "A new private key has been generated in slot '%s'." gen_out_pk_1 = "The corresponding public key has been saved to:\n%s" gen_out_csr_1 = "A certificate signing request has been saved to:\n%s" gen_out_ssc = "A self-signed certificate has been loaded." gen_out_ca = "A certificate from the CA has been loaded." import_from_file = "Import from file..." import_from_file_warning_1 = "Anything currently in slot '%s' will be " \ "overwritten by the imported content. This action cannot be undone." importing_file = "Importing from file..." unsupported_file = "Unsupported file type" delete_cert = "Delete certificate" delete_cert_warning_1 = "This will delete the certificate and key stored in " \ "slot '%s' of your YubiKey, and cannot be undone." deleting_cert = "Deleting certificate..." cert_exported = "Certificate exported" cert_exported_desc_1 = "Certificate exported to file: %s" cert_deleted = "Certificate deleted" cert_deleted_desc = "Certificate deleted successfully" cert_not_loaded = "No certificate loaded." cert_expires_1 = "Certificate expires: %s" cert_installed = "Certificate installed" cert_installed_desc = "A new certificate has been installed. You may need to " \ "unplug and re-insert your YubiKey before it can be used." cert_tmpl = "Certificate Template" subject = "Subject" error = "Error" wrong_key = "Incorrect management key" communication_error = "Communication error with the device" ykpiv_error_2 = "YkPiv error %d: %s" wrong_pin_tries_1 = "PIN verification failed. %d tries remaining" wrong_puk_tries_1 = "PUK verification failed. %d tries remaining" pin_blocked = "Your PIN has been blocked due to too many incorrect attempts." pin_too_long = "PIN must be no more than 8 characters long.\n" \ "NOTE: Special characters may be counted more than once." puk_too_long = "PUK must be no more than 8 characters long.\n" \ "NOTE: Special characters may be counted more than once." certreq_error = "There was an error requesting a certificate." certreq_error_1 = "Error running certreq: %s" ca_not_connected = "You currently do not have a connection to a " \ "Certification Authority." authentication_error = "Unable to authenticate to device" use_complex_pins = "Enforce complex PIN/PUKs" pin_expires = "Force periodic PIN change" pin_expires_days = "How often (days)?" issued_to_label = "Issued to:" issued_by_label = "Issued by:" valid_from_label = "Valid from:" valid_to_label = "Valid to:" usage_9a = "The X.509 Certificate for PIV Authentication and its associated " \ "private key, as defined in FIPS 201, is used to authenticate the card " \ "and the cardholder." usage_9c = "The X.509 Certificate for Digital Signature and its associated " \ "private key, as defined in FIPS 201, support the use of digital " \ "signatures for the purpose of document signing. " usage_9d = "The X.509 Certificate for Key Management and its associated " \ "private key, as defined in FIPS 201, support the use of encryption for " \ "the purpose of confidentiality." usage_9e = "FIPS 201 specifies the optional Card Authentication Key (CAK) as " \ "an asymmetric or symmetric key that is used to support additional " \ "physical access applications. " algorithm = "Algorithm" alg_rsa_1024 = "RSA (1024 bits)" alg_rsa_2048 = "RSA (2048 bits)" alg_ecc_p256 = "ECC (P-256)" alg_ecc_p384 = "ECC (P-384)" algorithm_1 = "Algorithm: %s" output = "Output" out_pk = "Public key" out_csr = "Certificate Signing Request (CSR)" out_ssc = "Create a self-signed certificate" out_ca = "Request a certificate from a Windows CA" no_output = "Your configuration does not allow any valid output format." invalid_subject = "Invalid subject" invalid_subject_desc = """The subject must be written as: /CN=host.example.com/OU=test/O=example.com""" usage_policy = "Usage policy" pin_policy = "Require PIN" pin_policy_1 = "Require PIN: %s" pin_policy_default = "Slot default" pin_policy_never = "Never" pin_policy_once = "Once" pin_policy_always = "Always" touch_policy = "Require button touch" touch_needed = "User action needed" touch_needed_desc = "You have chosen to require user interaction to use this " \ "certificate. Once you close this dialog, the light on your YubiKey " \ "will start slowly blinking. At that point please touch the button on " \ "your YubiKey." touch_prompt = "Touch the button now..." auth_cert = "Authentication certificate" auth_cert_desc = "Generate a certificate for authentication" expiration_date = "Expiration date" yubikey-piv-manager-1.3.0/pivman/piv.py0000644000076500000240000003077612754337070017745 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from pivman.libykpiv import YKPIV, ykpiv, ykpiv_state from pivman.piv_cmd import YkPivCmd from pivman import messages as m from pivman.utils import der_read from pivman.yubicommon.compat import text_type, int2byte from ctypes import (POINTER, byref, create_string_buffer, sizeof, c_ubyte, c_size_t, c_int) from binascii import a2b_hex, b2a_hex import re _YKPIV_MIN_VERSION = b'1.2.0' libversion = ykpiv.ykpiv_check_version(_YKPIV_MIN_VERSION) if not libversion: raise Exception('libykpiv >= %s required' % _YKPIV_MIN_VERSION) class DeviceGoneError(Exception): def __init__(self): super(DeviceGoneError, self).__init__(m.communication_error) class PivError(Exception): def __init__(self, code): message = ykpiv.ykpiv_strerror(code) super(PivError, self).__init__(code, message) self.code = code self.message = message def __str__(self): return m.ykpiv.ykpiv_error_2 % (self.code, self.message) class WrongPinError(ValueError): m_tries_1 = m.wrong_pin_tries_1 m_blocked = m.pin_blocked def __init__(self, tries): super(WrongPinError, self).__init__(self.m_tries_1 % tries if tries > 0 else self.m_blocked) self.tries = tries @property def blocked(self): return self.tries == 0 class WrongPukError(WrongPinError): m_tries_1 = m.wrong_puk_tries_1 m_blocked = m.puk_blocked def check(rc): if rc == YKPIV.PCSC_ERROR: raise DeviceGoneError() elif rc != YKPIV.OK: raise PivError(rc) def wrap_puk_error(error): match = TRIES_PATTERN.search(str(error)) if match: raise WrongPukError(int(match.group(1))) raise WrongPukError(0) KEY_LEN = 24 DEFAULT_KEY = a2b_hex(b'010203040506070801020304050607080102030405060708') CERT_SLOTS = { '9a': YKPIV.OBJ.AUTHENTICATION, '9c': YKPIV.OBJ.SIGNATURE, '9d': YKPIV.OBJ.KEY_MANAGEMENT, '9e': YKPIV.OBJ.CARD_AUTH } ATTR_NAME = 'name' TRIES_PATTERN = re.compile(r'now (\d+) tries') class YkPiv(object): def __init__(self, verbosity=0, reader=None): self._cmd = YkPivCmd(verbosity=verbosity, reader=reader) self._state = POINTER(ykpiv_state)() if not reader: reader = 'Yubikey' self._chuid = None self._ccc = None self._pin_blocked = False self._verbosity = verbosity self._reader = reader self._certs = {} check(ykpiv.ykpiv_init(byref(self._state), self._verbosity)) self._connect() self._read_status() if not self.chuid: try: self.set_chuid() except ValueError: pass # Not autheniticated, perhaps? if not self.ccc: try: self.set_ccc() except ValueError: pass # Not autheniticated, perhaps? def reconnect(self): check(ykpiv.ykpiv_disconnect(self._state)) self._reset() def _connect(self): check(ykpiv.ykpiv_connect(self._state, self._reader.encode('utf8'))) self._read_version() self._read_chuid() def _read_status(self): try: check(ykpiv.ykpiv_disconnect(self._state)) data = self._cmd.run('-a', 'status') lines = data.splitlines() chunk = [] while lines: line = lines.pop(0) if chunk and not line.startswith(b'\t'): self._parse_status(chunk) chunk = [] chunk.append(line) if chunk: self._parse_status(chunk) self._status = data finally: self._reset() def _parse_status(self, chunk): parts, rest = chunk[0].split(), chunk[1:] if parts[0] == b'Slot' and rest: self._parse_slot(parts[1][:-1], rest) elif parts[0] == b'PIN': self._pin_blocked = parts[-1] == '0' def _parse_slot(self, slot, lines): slot = slot.decode('ascii') self._certs[slot] = dict(l.strip().split(b':\t', 1) for l in lines) def _read_version(self): v = create_string_buffer(10) check(ykpiv.ykpiv_get_version(self._state, v, sizeof(v))) self._version = v.value def _read_chuid(self): try: chuid_data = self.fetch_object(YKPIV.OBJ.CHUID)[29:29 + 16] self._chuid = b2a_hex(chuid_data) except PivError: # No chuid set? self._chuid = None def _read_ccc(self): try: ccc_data = self.fetch_object(YKPIV.OBJ.CAPABILITY)[29:29 + 16] self._ccc = b2a_hex(ccc_data) except PivError: # No ccc set? self._ccc = None def __del__(self): check(ykpiv.ykpiv_done(self._state)) def _reset(self): self._connect() args = self._cmd._base_args if '-P' in args: self.verify_pin(args[args.index('-P') + 1]) if '-k' in args: self.authenticate(a2b_hex(args[args.index('-k') + 1])) @property def version(self): return self._version @property def chuid(self): return self._chuid @property def ccc(self): return self._ccc @property def pin_blocked(self): return self._pin_blocked @property def certs(self): return dict(self._certs) def set_chuid(self): try: check(ykpiv.ykpiv_disconnect(self._state)) self._cmd.run('-a', 'set-chuid') finally: self._reset() def set_ccc(self): try: check(ykpiv.ykpiv_disconnect(self._state)) self._cmd.run('-a', 'set-ccc') finally: self._reset() def authenticate(self, key=None): if key is None: key = DEFAULT_KEY elif len(key) != KEY_LEN: raise ValueError('Key must be %d bytes' % KEY_LEN) c_key = (c_ubyte * KEY_LEN).from_buffer_copy(key) check(ykpiv.ykpiv_authenticate(self._state, c_key)) self._cmd.set_arg('-k', b2a_hex(key)) if not self.chuid: self.set_chuid() def set_authentication(self, key): if len(key) != KEY_LEN: raise ValueError('Key must be %d bytes' % KEY_LEN) c_key = (c_ubyte * len(key)).from_buffer_copy(key) check(ykpiv.ykpiv_set_mgmkey(self._state, c_key)) self._cmd.set_arg('-k', b2a_hex(key)) def verify_pin(self, pin): if isinstance(pin, text_type): pin = pin.encode('utf8') buf = create_string_buffer(pin) tries = c_int(-1) rc = ykpiv.ykpiv_verify(self._state, buf, byref(tries)) if rc == YKPIV.WRONG_PIN: if tries.value == 0: self._pin_blocked = True self._cmd.set_arg('-P', None) raise WrongPinError(tries.value) check(rc) self._cmd.set_arg('-P', pin) def set_pin(self, pin): if isinstance(pin, text_type): pin = pin.encode('utf8') if len(pin) > 8: raise ValueError(m.pin_too_long) try: check(ykpiv.ykpiv_disconnect(self._state)) self._cmd.change_pin(pin) finally: self._reset() def reset_pin(self, puk, new_pin): if isinstance(new_pin, text_type): new_pin = new_pin.encode('utf8') if len(new_pin) > 8: raise ValueError(m.pin_too_long) if isinstance(puk, text_type): puk = puk.encode('utf8') try: check(ykpiv.ykpiv_disconnect(self._state)) self._cmd.reset_pin(puk, new_pin) except ValueError as e: wrap_puk_error(e) finally: self._reset() self._read_status() def set_puk(self, puk, new_puk): if isinstance(puk, text_type): puk = puk.encode('utf8') if isinstance(new_puk, text_type): new_puk = new_puk.encode('utf8') if len(new_puk) > 8: raise ValueError(m.puk_too_long) try: check(ykpiv.ykpiv_disconnect(self._state)) self._cmd.change_puk(puk, new_puk) except ValueError as e: wrap_puk_error(e) finally: self._reset() def reset_device(self): try: check(ykpiv.ykpiv_disconnect(self._state)) self._cmd.run('-a', 'reset') finally: del self._cmd def fetch_object(self, object_id): buf = (c_ubyte * 4096)() buf_len = c_size_t(sizeof(buf)) check(ykpiv.ykpiv_fetch_object(self._state, object_id, buf, byref(buf_len))) return b''.join(map(int2byte, buf[:buf_len.value])) def save_object(self, object_id, data): c_data = (c_ubyte * len(data)).from_buffer_copy(data) check(ykpiv.ykpiv_save_object(self._state, object_id, c_data, len(data))) def generate(self, slot, algorithm, pin_policy, touch_policy): try: check(ykpiv.ykpiv_disconnect(self._state)) return self._cmd.generate(slot, algorithm, pin_policy, touch_policy) finally: self._reset() def create_csr(self, subject, pubkey_pem, slot): try: check(ykpiv.ykpiv_disconnect(self._state)) return self._cmd.create_csr(subject, pubkey_pem, slot) finally: self._reset() def create_selfsigned_cert(self, subject, pubkey_pem, slot, valid_days=365): try: check(ykpiv.ykpiv_disconnect(self._state)) return self._cmd.create_ssc(subject, pubkey_pem, slot, valid_days) finally: self._reset() def import_cert(self, cert_pem, slot, frmt='PEM', password=None): try: check(ykpiv.ykpiv_disconnect(self._state)) return self._cmd.import_cert(cert_pem, slot, frmt, password) finally: self._reset() self._read_status() def import_key(self, cert_pem, slot, frmt, password, pin_policy, touch_policy): try: check(ykpiv.ykpiv_disconnect(self._state)) return self._cmd.import_key(cert_pem, slot, frmt, password, pin_policy, touch_policy) finally: self._reset() def sign_data(self, slot, hashed, algorithm=YKPIV.ALGO.RSA2048): c_hashed = (c_ubyte * len(hashed)).from_buffer_copy(hashed) buf = (c_ubyte * 4096)() buf_len = c_size_t(sizeof(buf)) check(ykpiv.ykpiv_sign_data(self._state, c_hashed, len(hashed), buf, byref(buf_len), algorithm, int(slot, 16))) return ''.join(map(int2byte, buf[:buf_len.value])) def read_cert(self, slot): try: data = self.fetch_object(CERT_SLOTS[slot]) except PivError: return None cert, rest = der_read(data, 0x70) zipped, rest = der_read(rest, 0x71) if zipped != b'\0': pass # TODO: cert is compressed, uncompress. return cert def delete_cert(self, slot): if slot not in self._certs: raise ValueError('No certificate loaded in slot: %s' % slot) try: check(ykpiv.ykpiv_disconnect(self._state)) self._cmd.delete_cert(slot) del self._certs[slot] finally: self._reset() yubikey-piv-manager-1.3.0/pivman/piv_cmd.py0000644000076500000240000001415112754337070020555 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. import subprocess import sys import os CMD = 'yubico-piv-tool' if getattr(sys, 'frozen', False): # we are running in a PyInstaller bundle basedir = sys._MEIPASS else: # we are running in a normal Python environment basedir = os.path.dirname(__file__) def find_cmd(): def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) cmd = CMD + '.exe' if subprocess.mswindows else CMD paths = [basedir] + os.environ.get('PATH', '').split(os.pathsep) for path in paths: path = path.strip('"') fpath = os.path.join(path, cmd) if is_exe(fpath): return fpath return None def check(status, err): if status != 0: raise ValueError('Error: %s' % err) def set_arg(args, opt, value): args = list(args) if opt != '-a' and opt in args: index = args.index(opt) if value is None: del args[index] del args[index] else: args[index + 1] = value elif value is not None: args.extend([opt, value]) return args class YkPivCmd(object): def __init__(self, cmd=find_cmd(), verbosity=0, reader=None, key=None): self._base_args = [cmd] if verbosity > 0: self._base_args.extend(['-v', str(verbosity)]) if reader: self._base_args.extend(['-r', reader]) if key: self._base_args.extend(['-k', key]) def set_arg(self, opt, value): if isinstance(value, bytes): value = value.decode('utf8') self._base_args = set_arg(self._base_args, opt, value) def run(self, *args, **kwargs): if subprocess.mswindows: # Avoid showing console window on Windows startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW else: startupinfo = None full_args = list(self._base_args) new_args = list(args) while new_args: full_args = set_arg(full_args, new_args.pop(0), new_args.pop(0)) if '-k' in full_args: # Workaround for passing key in 1.1.0 i = full_args.index('-k') full_args = full_args[:i] + ['-k' + full_args[i+1]] \ + full_args[i+2:] p = subprocess.Popen(full_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo) out, err = p.communicate(**kwargs) check(p.returncode, err) return out def status(self): return self.run('-a', 'status') def change_pin(self, new_pin): if '-P' not in self._base_args: raise ValueError('PIN has not been verified') self.run('-a', 'change-pin', '-N', new_pin) self.set_arg('-P', new_pin) def change_puk(self, old_puk, new_puk): self.run('-a', 'change-puk', '-P', old_puk, '-N', new_puk) def reset_pin(self, puk, new_pin): self.run('-a', 'unblock-pin', '-P', puk, '-N', new_pin) self.set_arg('-P', new_pin) def generate(self, slot, algorithm, pin_policy, touch_policy): return self.run('-s', slot, '-a', 'generate', '-A', algorithm, '--pin-policy', pin_policy, '--touch-policy', 'always' if touch_policy else 'never') def create_csr(self, subject, pem, slot): if '-P' not in self._base_args: raise ValueError('PIN has not been verified') return self.run('-a', 'verify-pin', '-s', slot, '-a', 'request-certificate', '-S', subject, input=pem) def create_ssc(self, subject, pem, slot, valid_days=365): if '-P' not in self._base_args: raise ValueError('PIN has not been verified') return self.run('-a', 'verify-pin', '-s', slot, '-a', 'selfsign-certificate', '-S', subject, '--valid-days', str(valid_days), input=pem) def import_cert(self, data, slot, frmt='PEM', password=None): return self._do_import('import-cert', data, slot, frmt, password) def import_key(self, data, slot, frmt, password, pin_policy, touch_policy): return self._do_import('import-key', data, slot, frmt, password, '--pin-policy', pin_policy, '--touch-policy', 'always' if touch_policy else 'never') def _do_import(self, action, data, slot, frmt, password, *args): if '-k' not in self._base_args: raise ValueError('Management key has not been provided') args = ['-s', slot, '-K', frmt, '-a', action] + list(args) if password is not None: args.extend(['-p', password]) return self.run(*args, input=data) def delete_cert(self, slot): if '-k' not in self._base_args: raise ValueError('Management key has not been provided') return self.run('-s', slot, '-a', 'delete-certificate') yubikey-piv-manager-1.3.0/pivman/qt_resources.py0000644000076500000240000001734512755276415021670 0ustar dagstaff00000000000000# -*- coding: utf-8 -*- # Resource object code # # Created: Thu Aug 18 11:17:01 2016 # by: The Resource Compiler for PySide (Qt v4.8.7) # # WARNING! All changes made in this file will be lost! from PySide import QtCore qt_resource_data = b"\x00\x00\x00\x5c\x0a\x0apivman.png\x0aqt_resources.qrc\x0a\x0a\x0a\x00\x00\x09R\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\xde\x0a\x0e\x0b9/\xe7\x8f\xe7o\x00\x00\x00\x1diTXtComment\x00\x00\x00\x00\x00Created with GIMPd.e\x07\x00\x00\x08\xb6IDATh\xde\xc5\x9amlS\xe7\x15\xc7\x7f\xf7\xda\xb1\x89\xf3B^L\xb2\x94\x10')I\x03\x1d/\xa5\xa5I \x14\xe7\xcb\x90h\xd9P\xa5\xee\x1d4Qi\xd3\xb4\x0f\xdb:WUU\x98T!\xfa\xa12H\x93\xa6IU\xa5\x22\x15iUWib\xeb\xba\xa9l\xab\x03iF\x15\xc2\xebX\x03\x09\x858$,$!\xc1\x8e\x13'\xf6}\xd9\x87<\x09\xbe\xbe\xd7\x8e\x1d\xdc\xee\xe4S\xec{\xcfs\xces\xces\xce\xff\xfc\x1fK\xe4P\xfc\x1d\xed\x0e`\x05\xe0\x04lI_\xab\xc0\x1c0\xeb\xf3\x06b\xb9ZS\xca\x81\xd1%\xc03\xc0\x16\xa0\x09\xa8\x03V\x03\xc5\x09\xfau \x0c\x0c\x03\xb7\x80k@\x0f\xd0\xe5\xf3\x06&\xff/\x0e\xf8;\xda\xeb\x81\x83\xc0nal~\x96*\xa2\xc0\x14\xf0\x11\xf0\xba\xcf\x1b\x08~%\x0e\xf8;\xda\xd7\x00\xaf\x02?%\xb7\xf2\x16\xf0\x06p\xdb\xe7\x0d\xe89w\xc0\xdf\xd1^\x08\xbc\x04\xfc\x1c(\xe3\xcb\x91\x09\xe07\xc01\x9f7\x10\xc9\x99\x03\xfe\x8e\xf6J\xe0\xaf\x22\xcf\xd3\x8a\xaekhh\xe8\xba&R\x7f~\x19I\x92\x91\x91\x91$9\x93%/\x02\xbb}\xde\xc0\xc8C;\xe0\xefh\xdf\x0c\x9c\x03\xec\x96\x06\xa3#\x01\x0e\x9b\x0b\x87\xdd\x85\xdbUGeQ#e.\x0f\xf9y\xc5\xf3\xc9\x1e\x0f31\x13\xe4n\xa4\x9f\xb1\xe9\x9b\xc4\x95\x19b\xea\x0c\xfa\xbck\xa9\x96\x8e\x01O\xfb\xbc\x81\xcb\xcbv\xc0\xdf\xd1\xfeM\xe0\x0f\xa2,\x9aLW\xb4\x18\x95\x85\x8d4\xacj\xc3S\xba\x95\xaa\xa2u\xd8\xe4\xbc\xb4\x1b\xa2\xe9\x0aw\xc2\xbd\x04'\xce\xd1?\xde\xc9H\xa4\x8f<\xd9\x91\xca\x94Y\xe0{>o\xe0d\xd6\x0e\x88\x9d\xff\xcc\xcaxMW\xb1I6v5\xbdBC\xf9\x0e\x1cv\xd7\xb2\x12>\xa6\xce\xd0?v\x86\x8f\xaf\xbf\x89\xaa\xab\xc8\x92-U$\x9a}\xde\xc0\xa5\x8c\x1d\x109?\x94*m\xaaWn`\xef\xd7\x8f\xe0\xb4\x17\xe6\xe4\xe4\xce*\x11N^=\xc8p\xe8J\xaaG\xe2\xc0\x1a\x9f7pwI\x07D\xb59m>\xb0:\xaa\xa6\xb0\xad\xeeG\xb4\xd6\xec_2U\xb2\x15MW8\x1b|\x97\xae\x81\xe3\xd8$\xbb\x95i=\x80\xd7\xe7\x0dL'~hU\x12^\xb2\xaa6\xaa\xa6\xf0\xdc\xfa_\xd3V\xfbb\xce\x8d\x07\x90%;\xdbk\x0f\xf0\xec\xbaC\xa8\xbab\xf5\xc8S\x80/m\x04D\x93\xbadU\xe7\x9b=?\xa0\xad\xf6E\xc39\xd0t\xd5\xb4\x8aM\xceKWYP\xb5\x18\xba\xc9x\x19Yz\x90\xad]\x03\xc7\xf9,x\xc2\xea\xf5\x10\xb0\xd1\xe7\x0d\x0c.|\x90\x9c\xe3\xaf&\x1b\xaf\xe9*5%\x9bi\xad\xd9ox\xf0\xbf\xe1\xcf\xe9\xbe\xfd\x1e6\xe9A4\x14m\x8e\xe6\x9a\xef\xb3z\xe5FK\xe3#\xb1q\xfe\xdew\xcc\xf0\x8e\xaa+lzd\x0f\xf5e-\x8b\x9f\xb5x\xf61\x14\xba\xcc\xe0\xe4\xc5\xe4\x83\xbd\x12x\x0d\xf8\x89)\x02\x02\xdb|\x91\x9c\xf7\x12\x12?\xdb\xfeg\xd3\x81\xd5t\x0d\x7f\xc7\xce\xf9\x05\xa4y5\x9a\x16\xa7\xae\xbc\x85\x176\xfa-\x1d8\x1b<\xc1\xe9/~\x87\xdd\xb6b\xa1\xeb\xa1\xe81~\xf5\xcc?\xc9\xb3\x19\xa1\xd4\x9c2\xc5o\xbb\xbe%\x9a\xa1)\xa2\xb5\x0b\xd8)\xf1\x0c\x1cL~J\xd1b\xecjz\xc5\xb2\xda\xc8\x92\xcc\x96\xea\xe7Qu\x05I\xfc\xd9d\x077\xc6?%\x1a\x0fY:pi\xf8\x8f\xd8e\xe7\xe2\xf3\x9a\xae\xb0\xa9j\x8f\xc9x\x00\xa7\xbd\x88]\x8d/\xa3h\x96\xc8\xfb5\xc3!\x16\x90xwr\x87\xad,l\xa4\xa1|G\xca|\xde\xb2\xfayQ1\x1e\x88]v\xd0}\xfb\xf7\xa6g\x87BW\x08\xcd\x8e\x18\xa0\x84$\xd9x\xb2\xfa\x85\x94\xfa\x1b+\xbcT\x146\xa0\x9bN\x0d{\xfc\x1d\xed\xc5\x89\x11xF@b\xc3\xe9nX\xd5\x96\xb6I\x159+XS\xfa\x84a\x01Y\xb2s}\xf44\xaa\x167<{~\xe8\x03\xec\xb2\xd3\xb0A5%\x9b)\xcd_\x9dR\xbf\xc3\x96\xcfc\xabvZ\x95\x84\x95\xc2\xe6E\x07\xb6$\xe3y\x87\xcd\x85\xa7tk\xda\xd2\x97g\xcb\xa7\xbe\xac\xc5Tu\xa2\xf1\x10\xc1\xc9\xf3\x09\xff\xdfg(\xf4o\xe3\xee#Q_\xdej\x99>\x89\xe2)\xdd\x8a\xc3f\xda\xc4|QV\x91\xc5\x18\xd8d\xf2\xde\xee\xa2\xaah\xdd\x92\xf5\xbb\xbe\xbc\x05\x87\xbd\xc0\xd86\xb5Y\x06&\xcf\x09D\x0a\xc1\xc9\x0b\xc4\x95\x99\xa4\x1c/`\xad{\xc7\x92\xfa\xab\x8a\x9bL\xfa\x85\xac\xf7w\xb4;e1\xc3\xd6%Cb\xb7\xab.\xa3\x86U\xee\xf2PQ\xb8v\xd1\xd8\x05\x19\x98\xe8fN\x9dF\xd3U\x06\xef_0\x1cF]\xd7x\xa4xC\xda\xf4ILIwA\x9dI?P\x0f\xac\x90\x05X3h\xd2\xd0\xa8,j\xcc\xb8\x8b>\xbd\xe6\xbb\xa8z\xdc\x90\x1ew#7\xb87\x1d$\xa6N\x13\x9c\xe8A\x92\xa4\x84\xda\x1f\xa7\xb5\xf6\x87\x19\xeb\xaf,jD\xc3\xe4\xc0j\xc0!\x0b\xf6\xa089\x02e.O\xc6\x0b\xd4\x955S\x92\xbf\xda\xb0K6\xc9N\xcf\xed\xf7\x99\x98\x19\xe4\xde\xcc\xe0b-\xd7t\x95\xaa\xa2&\xaa\x8a\xd6g\xac\xbf,\xbf\xc6*\x02\xc5\x80]\xb6\x06u\xfa\xe20\x92\xa9\xb4z\xf6\x13\xd7\xe6\x0c\x90\xe2\xc6\xbd\x7f\xf1\xe9\xadw\x0c\xa9\xa8hsl\xab=\x90\x95\xee\x15\xf6\xc2\x84\xe9\xce\x88\xe3dr$\x0d\xee6\x0a\x1de\xa6\xa1gp\xf2B\x02\x1c\xd0)s\xd5P\xbdrC\xee@`\x02oc\xe8\x02\xd1x8+EN{!\x8f\xba\xb7\x99B\x9dX:u\xa0\xc1\xbd#\xeb9bN\x89X\xc1\x09m\xc1\x01U\x90N\x86E'f\xb2\xa3id\xc9N]i3v\xd9\x91\xb61=Z\xbe-\xd3\xc1~Q\xeeE\x07\xad\xde\x09\x03\x8a,\xe8\xbeacXd\xeeF\xfa\xb3\x0eg]ys\xda\xb3S\xe8t\xe3)}2k\xbd\xa3S\xfd\xc8\xe6l\x1f\x02b\xb2\x18\x9co%G`l\xfa&\x9a\xf5`\x91\xe6\xb0\x15QW\xdej9'(\xda\x1c[\xab\xbf\x9d\xb5\xf1\xaa\xae06}\xd3*\x02\xb7\x80YY\x10\xad\xd7LC\xa82\xc3\x9dpo\xd6\x0b\xb6z\xf6\x19z\xc2\x02\xeeq\xd8]l\xa8z.k}#\xe1^b\xca\xb4\xd5W\xbd>o`NN\x987\xa3\xc9\x8cAp\xe2\x5c\xd6\x0b\xde\x09\x7f\x8e\x94\x14nU\x8b\xd1R\xb3oYU&8\xd9\xc3\x9c:c\xc5\xabv'V\xa1.A\xb4\x1a*F\xffx'1\xf3\xcbi\xa5{\xf0=\x13\x04q\xda\x0bY_\xf9\x8d\xac\x8d\x8f\xabQ\xae\x8f\x9d\xb6\xfa*\x0ct.: (\xee\x8f\x8c\x85Tb$\xd2G\xff\xd8\x99\x8c\x17\xbc}\xff2\x93\xd1!\x03:\xd5u\x8d\xda\xd2\xad\x148\xb2\xa7S\xaf\x8d~\xc2h\xa4\xdfj\xc6\xfe\xd0\xe7\x0d\x84\x93\x1b\xd9\xeb&\xb8,;\xf8\xf8\xfa\x9b\xcc*K\xf3\xac::7\xc6;M\xf9j\x93\xf3X\xebnK[^\xad\xb9\xa2)N\xf5\x1dM\xf5\xdea\x13\xad\x22f\xcc\xb7\x92\x1b\x9a\xaa\xab\x9c\xbczp\xc9\x8a\x14S\xa6\xe9\x1f\xef4\x80\xb6\xc5\x06W\xbe-k\x8e\xe8\xe4\xd5C\xa2\x9a\x99v\xff\xedDV\x22\xb96\xbd!(\xee\x84\x06ec8t\x85\xb3\xc1w\xd3.\xda;\xfa\x0fF\x22}\xc4\xd4(1u\x9e\xbc\x8d*a\xd6\xba\xdb\xb2\xc6Ug\x83'\x18\x0e]\xb1\xa2\x1aC\xc0\x11\xc3\x08\x9b\x9c\xc6\x82\x9f7\xa5S\xd7\xc0qJ\xf2\xaby<\xc5alp\xef\xe0\x17m\x7f3%V\x81\xc3\x9d\x95\xf1\xff\xb9{\x8a\xae\x81w\x0c\xd4K\x82\x1cK\xbe\xc9IE-\x9e\x01\x9e0Q\x8b\xba\xc2\xf6\xda\x03\xb4x\xf6\x99\x86\xf9\xdcP\x8b'\x84\xf1\x96\xd4\xe2y`g2\xb5\x98\x8a\xdc\xfd\x1a\x10\x04,OPu\xc9F\xf6>~\x18\xa7\xbd(G\xe4\xee\x14'\xaf\x1eJG\xee\xc6\x80\x9a\x8c\xc8\xdd\x04'6\x09z}\x85\x15\xbd.K6v5\xbeLc\x85\x17\x87-\x7fY\x86\xc7\xd5(\xd7F?\xe1T\xdf\xd1E\x9dV`\x14h\xf5y\x03\x17\xb3\xba\x1f\x10N\xec\x05\xde\xb7\x8e\xc4\xfc\x05GEa\x03\x8f\xad\xda9\x7f\xc1Q\xdcd\xe08Sa\x9b\x91p/\xc1\xc9\x1e\xae\x8f\x9df4\xd2/J\xa5\x94\xca\xf8\xef\xf8\xbc\x81?\xa5\xd2\x97\xe9\x15S7\x90\x97\xaa\xfe?\xb8b*\xc0] \xae\x98\xf2k\xc4$5\x8f\xe7\xefE\x07\x19\x9d\x9a\xbfb\x8a)\xd3\x8b\xf0`\x89+\xa6\x96T;\x9f\xb1\x03\xc2\x89J\xe0/\x0b\x5cL\xda\x86\x96\x9bK\xbe\xf3\xc0\xb3V9\xbf,\x07\x84\x13\x05\x82\x9f\xff\xa5`\xc6\xbe\x0c\x09\x01\xc7\x80\xa3\xc9\xd5\xe6\xa1\x1dHp\xa4F\x90\xab?\xce\xb1\xf1o\x03G\xb2\xbd\xb1\x7f\x98\x9f\x1ax\x84#{DD\x96\xf3S\x830\xf0!p8\x11\x1e|%\x0e$8R,\x88\xd6\xa7\x80\xf5\x821[\xf8\xb1\x87\x9c0\x80\x87\xc5\x18x\x0b\xe8\x15\x85\xa1s\x01U.W\xa4\x5c\xe6\x80\xbf\xa3\xdd)\xfa\x86\xc3\x02\xa6(\xa2\xb2\xcc\xfa\xbc\x81\xb9\x5c\xad\xf9?\xb4\xddFJx\xea]\xf1\x00\x00\x00\x00IEND\xaeB`\x82" qt_resource_name = b"\x00\x10\x08X\xa8#\x00q\x00t\x00_\x00r\x00e\x00s\x00o\x00u\x00r\x00c\x00e\x00s\x00.\x00q\x00r\x00c\x00\x0a\x03\x8f\xb6\xe7\x00p\x00i\x00v\x00m\x00a\x00n\x00.\x00p\x00n\x00g" qt_resource_struct = b"\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00&\x00\x00\x00\x00\x00\x01\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00" def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() yubikey-piv-manager-1.3.0/pivman/storage.py0000644000076500000240000000772512752610266020610 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. import os from pivman import messages as m from pivman.piv import CERT_SLOTS from pivman.yubicommon import qt from PySide import QtCore from collections import namedtuple from getpass import getuser from sys import platform __all__ = [ 'CONFIG_HOME', 'SETTINGS', 'settings' ] CONFIG_HOME = os.path.join(os.path.expanduser('~'), '.pivman') Setting = namedtuple('Setting', 'key default type') win = platform == 'win32' def default_outs(): if win: return ['ssc', 'csr', 'ca'] else: return ['ssc', 'csr'] class SETTINGS: ALGORITHM = Setting('algorithm', 'RSA2048', str) CARD_READER = Setting('card_reader', None, str) CERTREQ_TEMPLATE = Setting('certreq_template', None, str) COMPLEX_PINS = Setting('complex_pins', False, bool) ENABLE_IMPORT = Setting('enable_import', True, bool) OUT_TYPE = Setting('out_type', 'ca' if win else 'ssc', str) PIN_AS_KEY = Setting('pin_as_key', True, bool) PIN_EXPIRATION = Setting('pin_expiration', 0, int) PIN_POLICY = Setting('pin_policy', None, str) PIN_POLICY_SLOTS = Setting('pin_policy_slots', [], list) SHOWN_OUT_FORMS = Setting('shown_outs', default_outs(), list) SHOWN_SLOTS = Setting('shown_slots', sorted(CERT_SLOTS.keys()), list) SUBJECT = Setting('subject', '/CN=%s' % getuser(), str) TOUCH_POLICY = Setting('touch_policy', False, bool) TOUCH_POLICY_SLOTS = Setting('touch_policy_slots', sorted(CERT_SLOTS.keys()), list) class SettingsOverlay(object): def __init__(self, master, overlay): self._master = master self._overlay = overlay def __getattr__(self, method_name): return getattr(self._overlay, method_name) def rename(self, new_name): raise NotImplementedError() def value(self, setting, default=None): """Give preference to master.""" key, default, d_type = setting val = self._master.value(key, self._overlay.value(key, default)) if not isinstance(val, d_type): val = qt.convert_to(val, d_type) return val def setValue(self, setting, value): self._overlay.setValue(setting.key, value) def remove(self, setting): self._overlay.remove(setting.key) def childKeys(self): """Combine keys of master and overlay.""" return list(set(self._master.childKeys() + self._overlay.childKeys())) def is_locked(self, setting): return self._master.contains(setting.key) def __repr__(self): return 'Overlay(%s, %s)' % (self._master, self._overlay) settings = qt.PySettings(SettingsOverlay( QtCore.QSettings(m.organization, m.app_name), qt.Settings.wrap(os.path.join(CONFIG_HOME, 'settings.ini'), QtCore.QSettings.IniFormat).get_group('settings') )) yubikey-piv-manager-1.3.0/pivman/utils.py0000644000076500000240000001070212754337056020276 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from getpass import getuser from pivman import messages as m from pivman.yubicommon.compat import byte2int import re import subprocess import os import tempfile def has_ca(): try: if subprocess.mswindows: startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW p = subprocess.Popen( ['certutil', '-ping'], stdout=subprocess.PIPE, startupinfo=startupinfo) return p.returncode == 0 except OSError: pass return False def request_cert_from_ca(csr, cert_tmpl): try: with tempfile.NamedTemporaryFile(delete=False) as f: f.write(csr) csr_fn = f.name with tempfile.NamedTemporaryFile() as f: cert_fn = f.name p = subprocess.Popen(['certreq', '-submit', '-attrib', 'CertificateTemplate:%s' % cert_tmpl, csr_fn, cert_fn], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, _ = p.communicate() if p.returncode != 0: raise ValueError(m.certreq_error_1 % out) with open(cert_fn, 'r') as cert: return cert.read() except OSError as e: raise ValueError(m.certreq_error_1 % e) finally: os.remove(csr_fn) if os.path.isfile(cert_fn): os.remove(cert_fn) def test(fn, *args, **kwargs): e_type = kwargs.pop('catches', Exception) try: fn(*args, **kwargs) return True except e_type: return False # https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/504.mspx?mfr=true # Password must contain characters from three of the following four categories: CATEGORIES = [ lambda c: c.isupper(), # English uppercase characters (A through Z) lambda c: c.islower(), # English lowercase characters (a through z) re.compile(r'[0-9]').match, # Base 10 digits (0 through 9) re.compile(r'\W', re.UNICODE).match # Nonalphanumeric characters (e.g., !, $, #, %) ] def contains_category(password, category): return any(category(p) for p in password) def complexity_check(password): # Be at least six characters in length if len(password) < 6: return False # Contain characters from at least 3 groups: if sum(contains_category(password, c) for c in CATEGORIES) < 3: return False # Not contain all or part of the user's account name parts = [p for p in re.split(r'\W', getuser().lower()) if len(p) >= 3] if any(part in password.lower() for part in parts): return False return True def der_read(der_data, expected_t=None): t = byte2int(der_data[0]) if expected_t is not None and expected_t != t: raise ValueError('Wrong tag. Expected: %x, got: %x' % (expected_t, t)) l = byte2int(der_data[1]) offs = 2 if l > 0x80: n_bytes = l - 0x80 l = b2len(der_data[offs:offs + n_bytes]) offs = offs + n_bytes v = der_data[offs:offs + l] rest = der_data[offs + l:] if expected_t is None: return t, v, rest return v, rest def b2len(bs): l = 0 for b in bs: l *= 256 l += byte2int(b) return l yubikey-piv-manager-1.3.0/pivman/view/0000755000076500000240000000000012755276415017540 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/pivman/view/__init__.py0000644000076500000240000000231012752100505021624 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. yubikey-piv-manager-1.3.0/pivman/view/cert.py0000644000076500000240000003157512754337056021060 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui, QtCore, QtNetwork from pivman import messages as m from pivman.piv import PivError, DeviceGoneError from pivman.storage import settings, SETTINGS from pivman.view.utils import Dialog, get_text from pivman.view.generate_dialog import GenerateKeyDialog from pivman.view.usage_policy_dialog import UsagePolicyDialog from datetime import datetime from functools import partial SLOTS = { '9a': 'Authentication', '9c': 'Digital Signature', '9d': 'Key Management', '9e': 'Card Authentication', } USAGES = { '9a': m.usage_9a, '9c': m.usage_9c, '9d': m.usage_9d, '9e': m.usage_9e, } FILE_FILTER = 'Certificate/key files ' \ '(*.pfx *.p12 *.cer *.crt *.key *.pem *.der)' def detect_type(data, fn): suffix = '.' in fn and fn.lower().rsplit('.', 1)[1] f_format = None # pfx, pem or der f_type = 0 # 1 for certificate, 2 for key, 3 for both needs_password = False if suffix in ['pfx', 'p12']: f_format = 'pfx' needs_password = True else: f_format = 'pem' if data.startswith(b'-----') else 'der' if f_format == 'pem': if b'CERTIFICATE' in data and b'PRIVATE KEY' in data: f_type = 3 elif b'PRIVATE KEY' in data: f_type = 2 elif b'CERTIFICATE' in data: f_type = 1 needs_password = b'ENCRYPTED' in data elif suffix in ['cer', 'crt']: f_type = 1 elif suffix in ['key']: f_type = 2 else: certs = QtNetwork.QSslCertificate.fromData( data, QtNetwork.QSsl.Der) f_type = 1 if certs else 2 return f_type, f_format, needs_password def import_file(controller, slot, fn): with open(fn, 'rb') as f: data = f.read() f_type, f_format, needs_password = detect_type(data, fn) if f_type == 2 and f_format == 'der': return None, None, False # We don't know what type of key this is. def func(password=None, pin_policy=None, touch_policy=False): if f_format == 'pfx': controller.import_key(data, slot, 'PKCS12', password, pin_policy, touch_policy) controller.import_certificate(data, slot, 'PKCS12', password) elif f_format == 'pem': if f_type == 1: controller.import_certificate(data, slot, 'PEM', password) elif f_type == 2: controller.import_key(data, slot, 'PEM', password, pin_policy, touch_policy) elif f_type == 3: controller.import_certificate(data, slot, 'PEM', password) controller.import_key(data, slot, 'PEM', password, pin_policy, touch_policy) else: controller.import_certificate(data, slot, 'DER') return func, needs_password, f_type != 1 class CertPanel(QtGui.QWidget): def __init__(self, controller, slot, parent=None): super(CertPanel, self).__init__(parent) self._controller = controller self._slot = slot controller.use(self._build_ui) def _build_ui(self, controller): cert = controller.get_certificate(self._slot) layout = QtGui.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) status = QtGui.QGridLayout() status.addWidget(QtGui.QLabel(m.issued_to_label), 0, 0) issued_to = cert.subjectInfo(QtNetwork.QSslCertificate.CommonName) status.addWidget(QtGui.QLabel(issued_to), 0, 1) status.addWidget(QtGui.QLabel(m.issued_by_label), 0, 2) issued_by = cert.issuerInfo(QtNetwork.QSslCertificate.CommonName) status.addWidget(QtGui.QLabel(issued_by), 0, 3) status.addWidget(QtGui.QLabel(m.valid_from_label), 1, 0) valid_from = QtGui.QLabel(cert.effectiveDate().toString()) now = datetime.utcnow() if cert.effectiveDate().toPython() > now: valid_from.setStyleSheet('QLabel { color: red; }') status.addWidget(valid_from, 1, 1) status.addWidget(QtGui.QLabel(m.valid_to_label), 1, 2) valid_to = QtGui.QLabel(cert.expiryDate().toString()) if cert.expiryDate().toPython() < now: valid_to.setStyleSheet('QLabel { color: red; }') status.addWidget(valid_to, 1, 3) layout.addLayout(status) buttons = QtGui.QHBoxLayout() export_btn = QtGui.QPushButton(m.export_to_file) export_btn.clicked.connect(partial(self._export_cert, cert)) buttons.addWidget(export_btn) delete_btn = QtGui.QPushButton(m.delete_cert) delete_btn.clicked.connect( self._controller.wrap(self._delete_cert, True)) buttons.addWidget(delete_btn) layout.addStretch() layout.addLayout(buttons) def _export_cert(self, cert): fn, fn_filter = QtGui.QFileDialog.getSaveFileName( self, m.export_cert, filter='Certificate (*.pem *.crt)') if not fn: return with open(fn, 'wb') as f: f.write(cert.toPem().data()) QtGui.QMessageBox.information(self, m.cert_exported, m.cert_exported_desc_1 % fn) def _delete_cert(self, controller, release): res = QtGui.QMessageBox.warning(self, m.delete_cert, m.delete_cert_warning_1 % self._slot, QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel) if res == QtGui.QMessageBox.Ok: try: controller.ensure_authenticated() worker = QtCore.QCoreApplication.instance().worker worker.post( m.deleting_cert, (controller.delete_certificate, self._slot), partial(self._delete_cert_callback, controller, release), True) except (DeviceGoneError, PivError, ValueError) as e: QtGui.QMessageBox.warning(self, m.error, str(e)) def _delete_cert_callback(self, controller, release, result): if isinstance(result, DeviceGoneError): QtGui.QMessageBox.warning(self, m.error, m.device_unplugged) self.window().accept() elif isinstance(result, Exception): QtGui.QMessageBox.warning(self, m.error, str(result)) else: self.parent().refresh(controller) QtGui.QMessageBox.information(self, m.cert_deleted, m.cert_deleted_desc) class CertWidget(QtGui.QWidget): def __init__(self, controller, slot): super(CertWidget, self).__init__() self._controller = controller self._slot = slot self._build_ui() controller.use(self.refresh) def _build_ui(self): layout = QtGui.QVBoxLayout(self) self._status = QtGui.QLabel(m.cert_not_loaded) layout.addWidget(self._status) buttons = QtGui.QHBoxLayout() from_file_btn = QtGui.QPushButton(m.import_from_file) from_file_btn.clicked.connect( self._controller.wrap(self._import_file, True)) if settings[SETTINGS.ENABLE_IMPORT]: buttons.addWidget(from_file_btn) generate_btn = QtGui.QPushButton(m.generate_key) generate_btn.clicked.connect( self._controller.wrap(self._generate_key, True)) buttons.addWidget(generate_btn) layout.addLayout(buttons) def refresh(self, controller): if controller.pin_blocked: self.window().accept() return self.layout().removeWidget(self._status) self._status.hide() if self._slot in controller.certs: self._status = CertPanel(self._controller, self._slot, self) else: self._status = QtGui.QLabel('%s

%s' % ( USAGES[self._slot], m.cert_not_loaded)) self._status.setWordWrap(True) self.layout().insertWidget(0, self._status) def _import_file(self, controller, release): res = QtGui.QMessageBox.warning(self, m.import_from_file, m.import_from_file_warning_1 % self._slot, QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel) if res != QtGui.QMessageBox.Ok: return fn, fn_filter = QtGui.QFileDialog.getOpenFileName( self, m.import_from_file, filter=FILE_FILTER) if not fn: return func, needs_password, is_key = import_file(controller, self._slot, fn) if func is None: QtGui.QMessageBox.warning(self, m.error, m.unsupported_file) return if is_key: dialog = UsagePolicyDialog(controller, self._slot, self) if dialog.has_content and dialog.exec_(): func = partial(func, pin_policy=dialog.pin_policy, touch_policy=dialog.touch_policy) settings[SETTINGS.TOUCH_POLICY] = dialog.touch_policy if needs_password: password, status = get_text( self, m.enter_file_password, m.password_label, QtGui.QLineEdit.Password) if not status: return func = partial(func, password=password) try: if not controller.poll(): controller.reconnect() controller.ensure_authenticated() worker = QtCore.QCoreApplication.instance().worker worker.post(m.importing_file, func, partial( self._import_file_callback, controller, release), True) except (DeviceGoneError, PivError, ValueError) as e: QtGui.QMessageBox.warning(self, m.error, str(e)) def _import_file_callback(self, controller, release, result): if isinstance(result, DeviceGoneError): QtGui.QMessageBox.warning(self, m.error, m.device_unplugged) self.window().accept() elif isinstance(result, Exception): QtGui.QMessageBox.warning(self, m.error, str(result)) else: self.refresh(controller) QtGui.QMessageBox.information(self, m.cert_installed, m.cert_installed_desc) def _generate_key(self, controller, release): dialog = GenerateKeyDialog(controller, self._slot, self) if dialog.exec_(): self.refresh(controller) class CertDialog(Dialog): def __init__(self, controller, parent=None): super(CertDialog, self).__init__(parent) self.setWindowTitle(m.certificates) self._complex = settings[SETTINGS.COMPLEX_PINS] self._controller = controller controller.on_lost(self.accept) self._build_ui() def _build_ui(self): layout = QtGui.QVBoxLayout(self) # This unfortunately causes the window to resize when switching tabs. # layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) self._cert_tabs = QtGui.QTabWidget() self._cert_tabs.setMinimumSize(540, 160) shown_slots = settings[SETTINGS.SHOWN_SLOTS] selected = False for (slot, label) in sorted(SLOTS.items()): if slot in shown_slots: index = self._cert_tabs.addTab( CertWidget(self._controller, slot), label) if not selected: self._cert_tabs.setCurrentIndex(index) selected = True elif not settings.is_locked(SETTINGS.SHOWN_SLOTS): index = self._cert_tabs.addTab(QtGui.QLabel(), label) self._cert_tabs.setTabEnabled(index, False) layout.addWidget(self._cert_tabs) yubikey-piv-manager-1.3.0/pivman/view/generate_dialog.py0000644000076500000240000003000612754337757023230 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui, QtCore from pivman import messages as m from pivman.utils import has_ca, request_cert_from_ca from pivman.storage import settings, SETTINGS from pivman.piv import DeviceGoneError from pivman.view.usage_policy_dialog import UsagePolicyDialog from pivman.view.utils import SUBJECT_VALIDATOR def save_file_as(parent, title, fn_filter): return QtGui.QFileDialog.getSaveFileName(parent, title, filter=fn_filter)[0] def needs_subject(forms): return bool({'csr', 'ssc', 'ca'}.intersection(forms)) class GenerateKeyDialog(UsagePolicyDialog): def __init__(self, controller, slot, parent=None): super(GenerateKeyDialog, self).__init__(controller, slot, parent) def _build_ui(self): self.setWindowTitle(m.generate_key) self.setFixedWidth(400) layout = QtGui.QVBoxLayout(self) warning = QtGui.QLabel(m.generate_key_warning_1 % self._slot) warning.setWordWrap(True) layout.addWidget(warning) self._build_algorithms(layout) self._build_usage_policy(layout) self._build_output(layout) def _build_algorithms(self, layout): self._alg_type = QtGui.QButtonGroup(self) self._alg_rsa_1024 = QtGui.QRadioButton(m.alg_rsa_1024) self._alg_rsa_1024.setProperty('value', 'RSA1024') self._alg_rsa_2048 = QtGui.QRadioButton(m.alg_rsa_2048) self._alg_rsa_2048.setProperty('value', 'RSA2048') self._alg_ecc_p256 = QtGui.QRadioButton(m.alg_ecc_p256) self._alg_ecc_p256.setProperty('value', 'ECCP256') self._alg_ecc_p384 = QtGui.QRadioButton(m.alg_ecc_p384) self._alg_ecc_p384.setProperty('value', 'ECCP384') self._alg_type.addButton(self._alg_rsa_1024) self._alg_type.addButton(self._alg_rsa_2048) self._alg_type.addButton(self._alg_ecc_p256) if self._controller.version_tuple >= (4, 0, 0): self._alg_type.addButton(self._alg_ecc_p384) algo = settings[SETTINGS.ALGORITHM] if settings.is_locked(SETTINGS.ALGORITHM): layout.addWidget(QtGui.QLabel(m.algorithm_1 % algo)) else: layout.addWidget(self.section(m.algorithm)) for button in self._alg_type.buttons(): layout.addWidget(button) if button.property('value') == algo: button.setChecked(True) button.setFocus() if not self._alg_type.checkedButton(): button = self._alg_type.buttons()[0] button.setChecked(True) def _build_output(self, layout): layout.addWidget(self.section(m.output)) self._out_type = QtGui.QButtonGroup(self) self._out_pk = QtGui.QRadioButton(m.out_pk) self._out_pk.setProperty('value', 'pk') self._out_ssc = QtGui.QRadioButton(m.out_ssc) self._out_ssc.setProperty('value', 'ssc') self._out_csr = QtGui.QRadioButton(m.out_csr) self._out_csr.setProperty('value', 'csr') self._out_ca = QtGui.QRadioButton(m.out_ca) self._out_ca.setProperty('value', 'ca') self._out_type.addButton(self._out_pk) self._out_type.addButton(self._out_ssc) self._out_type.addButton(self._out_csr) out_btns = [] for button in self._out_type.buttons(): value = button.property('value') if value in settings[SETTINGS.SHOWN_OUT_FORMS]: layout.addWidget(button) out_btns.append(button) if value == settings[SETTINGS.OUT_TYPE]: button.setChecked(True) self._cert_tmpl = QtGui.QLineEdit(settings[SETTINGS.CERTREQ_TEMPLATE]) if 'ca' in settings[SETTINGS.SHOWN_OUT_FORMS]: if has_ca(): out_btns.append(self._out_ca) self._out_type.addButton(self._out_ca) self._out_ca.setChecked(True) layout.addWidget(self._out_ca) if not settings.is_locked(SETTINGS.CERTREQ_TEMPLATE): cert_box = QtGui.QHBoxLayout() cert_box.addWidget(QtGui.QLabel(m.cert_tmpl)) cert_box.addWidget(self._cert_tmpl) layout.addLayout(cert_box) else: layout.addWidget(QtGui.QLabel(m.ca_not_connected)) self._out_type.buttonClicked.connect(self._output_changed) buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) self._subject = QtGui.QLineEdit(settings[SETTINGS.SUBJECT]) self._subject.setValidator(SUBJECT_VALIDATOR) today = QtCore.QDate.currentDate() self._expire_date = QtGui.QDateTimeEdit(today.addYears(1)) self._expire_date.setDisplayFormat("yyyy-MM-dd") self._expire_date.setMinimumDate(today.addDays(1)) if not out_btns: layout.addWidget(QtGui.QLabel(m.no_output)) buttons.button(QtGui.QDialogButtonBox.Ok).setDisabled(True) else: if not settings.is_locked(SETTINGS.SUBJECT) and \ needs_subject([b.property('value') for b in out_btns]): subject_box = QtGui.QHBoxLayout() subject_box.addWidget(QtGui.QLabel(m.subject)) subject_box.addWidget(self._subject) layout.addLayout(subject_box) expire_date = QtGui.QHBoxLayout() expire_date.addWidget(QtGui.QLabel(m.expiration_date)) expire_date.addWidget(self._expire_date) layout.addLayout(expire_date) out_btn = self._out_type.checkedButton() if out_btn is None: out_btn = out_btns[0] out_btn.setChecked(True) self._output_changed(out_btn) buttons.accepted.connect(self._generate) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def _output_changed(self, btn): self._cert_tmpl.setEnabled(btn is self._out_ca) self._subject.setDisabled(btn is self._out_pk) self._expire_date.setDisabled(btn is not self._out_ssc) @property def algorithm(self): if settings.is_locked(SETTINGS.ALGORITHM): return settings[SETTINGS.ALGORITHM] return self._alg_type.checkedButton().property('value') @property def out_format(self): return self._out_type.checkedButton().property('value') def _generate(self): if self.out_format != 'pk' and not \ self._subject.hasAcceptableInput(): QtGui.QMessageBox.warning(self, m.invalid_subject, m.invalid_subject_desc) self._subject.setFocus() self._subject.selectAll() return if self.out_format == 'pk': out_fn = save_file_as(self, m.save_pk, 'Public Key (*.pem)') if not out_fn: return elif self.out_format == 'csr': out_fn = save_file_as(self, m.save_csr, 'Certificate Signing Reqest (*.csr)') if not out_fn: return else: out_fn = None try: if not self._controller.poll(): self._controller.reconnect() if self.out_format != 'pk': pin = self._controller.ensure_pin() else: pin = None self._controller.ensure_authenticated(pin) except Exception as e: QtGui.QMessageBox.warning(self, m.error, str(e)) if not isinstance(e, DeviceGoneError): self.accept() return valid_days = QtCore.QDate.currentDate().daysTo(self._expire_date.date()) worker = QtCore.QCoreApplication.instance().worker worker.post( m.generating_key, (self._do_generate, pin, out_fn, valid_days), self._generate_callback, True) def _do_generate(self, pin=None, out_fn=None, valid_days=365): data = self._controller.generate_key(self._slot, self.algorithm, self.pin_policy, self.touch_policy) return (self._do_generate2, data, pin, out_fn, valid_days) def _generate_callback(self, result): if isinstance(result, Exception): QtGui.QMessageBox.warning(self, m.error, str(result)) else: busy_message = m.generating_key if self.touch_policy and self.out_format in ['ssc', 'csr', 'ca']: QtGui.QMessageBox.information(self, m.touch_needed, m.touch_needed_desc) busy_message = m.touch_prompt worker = QtCore.QCoreApplication.instance().worker worker.post(busy_message, result, self._generate_callback2, True) def _do_generate2(self, data, pin, out_fn, valid_days=365): subject = self._subject.text() if self.out_format in ['csr', 'ca']: data = self._controller.create_csr(self._slot, pin, data, subject) if self.out_format in ['pk', 'csr']: with open(out_fn, 'w') as f: f.write(data) return out_fn else: if self.out_format == 'ssc': cert = self._controller.selfsign_certificate( self._slot, pin, data, subject, valid_days) elif self.out_format == 'ca': cert = request_cert_from_ca(data, self._cert_tmpl.text()) self._controller.import_certificate(cert, self._slot) def _generate_callback2(self, result): self.accept() if isinstance(result, Exception): QtGui.QMessageBox.warning(self, m.error, str(result)) else: settings[SETTINGS.ALGORITHM] = self.algorithm if self._controller.version_tuple >= (4, 0, 0): settings[SETTINGS.TOUCH_POLICY] = self.touch_policy settings[SETTINGS.OUT_TYPE] = self.out_format if self.out_format != 'pk' and not \ settings.is_locked(SETTINGS.SUBJECT): subject = self._subject.text() # Only save if different: if subject != settings[SETTINGS.SUBJECT]: settings[SETTINGS.SUBJECT] = subject if self.out_format == 'ca': settings[SETTINGS.CERTREQ_TEMPLATE] = self._cert_tmpl.text() message = m.generated_key_desc_1 % self._slot if self.out_format == 'pk': message += '\n' + m.gen_out_pk_1 % result elif self.out_format == 'csr': message += '\n' + m.gen_out_csr_1 % result elif self.out_format == 'ssc': message += '\n' + m.gen_out_ssc elif self.out_format == 'ca': message += '\n' + m.gen_out_ca QtGui.QMessageBox.information(self, m.generated_key, message) yubikey-piv-manager-1.3.0/pivman/view/init_dialog.py0000644000076500000240000002306612754341024022367 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui, QtCore from pivman import messages as m from pivman.piv import DeviceGoneError, PivError, KEY_LEN from pivman.view.utils import KEY_VALIDATOR, pin_field from pivman.utils import complexity_check from pivman.storage import settings, SETTINGS from pivman.yubicommon import qt from pivman.controller import AUTH_SLOT from binascii import b2a_hex import os class PinPanel(QtGui.QWidget): def __init__(self, headers): super(PinPanel, self).__init__() self._complex = settings[SETTINGS.COMPLEX_PINS] layout = QtGui.QFormLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addRow(headers.section(m.pin)) self._new_pin = pin_field() layout.addRow(m.new_pin_label, self._new_pin) self._confirm_pin = pin_field() layout.addRow(m.verify_pin_label, self._confirm_pin) @property def pin(self): error = None new_pin = self._new_pin.text() if not new_pin: error = m.pin_empty elif new_pin != self._confirm_pin.text(): error = m.pin_confirm_mismatch elif self._complex and not complexity_check(new_pin): error = m.pin_complexity_desc if error: self._new_pin.setText('') self._confirm_pin.setText('') self._new_pin.setFocus() raise ValueError(error) return new_pin class KeyPanel(QtGui.QWidget): def __init__(self, headers): super(KeyPanel, self).__init__() layout = QtGui.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(headers.section(m.management_key)) self._key_type = QtGui.QButtonGroup(self) self._kt_pin = QtGui.QRadioButton(m.use_pin_as_key, self) self._kt_key = QtGui.QRadioButton(m.use_separate_key, self) self._key_type.addButton(self._kt_pin) self._key_type.addButton(self._kt_key) self._key_type.buttonClicked.connect(self._change_key_type) layout.addWidget(self._kt_pin) layout.addWidget(self._kt_key) self._adv_panel = AdvancedPanel(headers) if settings[SETTINGS.PIN_AS_KEY]: self._kt_pin.setChecked(True) else: self._kt_key.setChecked(True) self.layout().addWidget(self._adv_panel) def _change_key_type(self, btn): if btn == self._kt_pin: self.layout().removeWidget(self._adv_panel) self._adv_panel.hide() self.adjustSize() self.parentWidget().adjustSize() else: self._adv_panel.reset() self.layout().addWidget(self._adv_panel) self._adv_panel.show() @property def use_pin(self): return self._key_type.checkedButton() == self._kt_pin @property def puk(self): return self._adv_panel.puk if not self.use_pin else None @property def key(self): return self._adv_panel.key if not self.use_pin else None class AdvancedPanel(QtGui.QWidget): def __init__(self, headers): super(AdvancedPanel, self).__init__() self._complex = settings[SETTINGS.COMPLEX_PINS] layout = QtGui.QFormLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addRow(QtGui.QLabel(m.key_label)) self._key = QtGui.QLineEdit() self._key.setValidator(KEY_VALIDATOR) self._key.textChanged.connect(self._validate_key) layout.addRow(self._key) buttons = QtGui.QDialogButtonBox() self._randomize_btn = QtGui.QPushButton(m.randomize) self._randomize_btn.clicked.connect(self.randomize) self._copy_btn = QtGui.QPushButton(m.copy_clipboard) self._copy_btn.clicked.connect(self._copy) buttons.addButton(self._randomize_btn, QtGui.QDialogButtonBox.ActionRole) buttons.addButton(self._copy_btn, QtGui.QDialogButtonBox.ActionRole) layout.addRow(buttons) layout.addRow(headers.section(m.puk)) self._puk = pin_field() layout.addRow(m.new_puk_label, self._puk) self._confirm_puk = pin_field() layout.addRow(m.verify_puk_label, self._confirm_puk) def reset(self): self.randomize() self._puk.setText('') self._confirm_puk.setText('') def randomize(self): self._key.setText(b2a_hex(os.urandom(KEY_LEN)).decode('ascii')) def _validate_key(self): self._copy_btn.setDisabled(not self._key.hasAcceptableInput()) def _copy(self): self._key.selectAll() self._key.copy() self._key.deselect() @property def key(self): if not self._key.hasAcceptableInput(): self._key.setText('') self._key.setFocus() raise ValueError(m.key_invalid_desc) return self._key.text() @property def puk(self): error = None puk = self._puk.text() if not puk: return None elif self._complex and not complexity_check(puk): error = m.puk_not_complex elif puk != self._confirm_puk.text(): error = m.puk_confirm_mismatch if error: self._puk.setText('') self._confirm_puk.setText('') self._puk.setFocus() raise ValueError(error) return puk class DefaultCertPanel(QtGui.QWidget): def __init__(self, headers): super(DefaultCertPanel, self).__init__() layout = QtGui.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(headers.section(m.auth_cert)) self._auth_cert_cb = QtGui.QCheckBox(m.auth_cert_desc) self._auth_cert_cb.setChecked(True) layout.addWidget(self._auth_cert_cb) class InitDialog(qt.Dialog): def __init__(self, controller, parent=None): super(InitDialog, self).__init__(parent) self.setWindowTitle(m.initialize) self.setMinimumWidth(400) self._controller = controller self._build_ui() def _build_ui(self): layout = QtGui.QVBoxLayout(self) self._pin_panel = PinPanel(self.headers) layout.addWidget(self._pin_panel) self._key_panel = KeyPanel(self.headers) if not settings.is_locked(SETTINGS.PIN_AS_KEY) or \ not settings[SETTINGS.PIN_AS_KEY]: layout.addWidget(self._key_panel) if AUTH_SLOT not in self._controller.certs: self._auth_cert_panel = DefaultCertPanel(self.headers) layout.addWidget(self._auth_cert_panel) layout.addStretch() buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok) self._ok_btn = buttons.button(QtGui.QDialogButtonBox.Ok) buttons.accepted.connect(self._initialize) layout.addWidget(buttons) def _initialize(self): try: pin = self._pin_panel.pin key = self._key_panel.key puk = self._key_panel.puk try: auth_cert = self._auth_cert_panel._auth_cert_cb.isChecked() except AttributeError: auth_cert = None if key is not None and puk is None: res = QtGui.QMessageBox.warning(self, m.no_puk, m.no_puk_warning, QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel) if res != QtGui.QMessageBox.Ok: return if not self._controller.poll(): self._controller.reconnect() self._controller.ensure_authenticated() worker = QtCore.QCoreApplication.instance().worker worker.post( m.initializing, (self._controller.initialize, auth_cert, pin, puk, key), self._init_callback, True ) except (DeviceGoneError, PivError, ValueError) as e: QtGui.QMessageBox.warning(self, m.error, str(e)) def _init_callback(self, result): if isinstance(result, DeviceGoneError): QtGui.QMessageBox.warning(self, m.error, m.device_unplugged) self.accept() elif isinstance(result, Exception): QtGui.QMessageBox.warning(self, m.error, str(result)) else: if not settings.is_locked(SETTINGS.PIN_AS_KEY): settings[SETTINGS.PIN_AS_KEY] = self._key_panel.use_pin self.accept() yubikey-piv-manager-1.3.0/pivman/view/main.py0000644000076500000240000001014312754337056021033 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui from PySide import QtCore from pivman import messages as m from pivman.watcher import ControllerWatcher from pivman.view.utils import IMPORTANT from pivman.view.init_dialog import InitDialog from pivman.view.set_pin_dialog import SetPinDialog from pivman.view.manage import ManageDialog from pivman.view.cert import CertDialog class MainWidget(QtGui.QWidget): def __init__(self): super(MainWidget, self).__init__() self._lock = QtCore.QMutex() self._controller = ControllerWatcher() self._build_ui() self._controller.on_found(self._refresh_controller, True) self._controller.on_lost(self._no_controller) self._no_controller() def showEvent(self, event): self.refresh() event.accept() def _build_ui(self): layout = QtGui.QVBoxLayout(self) btns = QtGui.QHBoxLayout() self._cert_btn = QtGui.QPushButton(m.certificates) self._cert_btn.clicked.connect(self._manage_certs) btns.addWidget(self._cert_btn) self._pin_btn = QtGui.QPushButton(m.manage_pin) self._pin_btn.clicked.connect(self._manage_pin) btns.addWidget(self._pin_btn) layout.addLayout(btns) self._messages = QtGui.QTextEdit() self._messages.setFixedSize(480, 100) self._messages.setReadOnly(True) layout.addWidget(self._messages) def _manage_pin(self): ManageDialog(self._controller, self).exec_() self.refresh() def _manage_certs(self): CertDialog(self._controller, self).exec_() self.refresh() def refresh(self): self._controller.use(self._refresh_controller, True) def _no_controller(self): self._pin_btn.setEnabled(False) self._cert_btn.setEnabled(False) self._messages.setHtml(m.no_key) def _refresh_controller(self, controller, release): if not controller.poll(): self._no_controller() return self._pin_btn.setEnabled(True) self._cert_btn.setDisabled(controller.pin_blocked) messages = [] if controller.pin_blocked: messages.append(IMPORTANT % m.pin_blocked) messages.append(m.key_with_applet_1 % controller.version.decode('ascii')) n_certs = len(controller.certs) messages.append(m.certs_loaded_1 % n_certs or m.no) self._messages.setHtml('
'.join(messages)) if controller.is_uninitialized(): dialog = InitDialog(controller, self) if dialog.exec_(): self.refresh() else: QtCore.QCoreApplication.instance().quit() elif controller.is_pin_expired() and not controller.pin_blocked: dialog = SetPinDialog(controller, self, True) if dialog.exec_(): self.refresh() else: QtCore.QCoreApplication.instance().quit() yubikey-piv-manager-1.3.0/pivman/view/manage.py0000644000076500000240000001312512752100505021323 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtCore, QtGui from pivman import messages as m from pivman.view.set_pin_dialog import (SetPinDialog, SetPukDialog, ResetPinDialog) from pivman.view.set_key_dialog import SetKeyDialog from pivman.view.utils import IMPORTANT, Dialog from pivman.storage import settings, SETTINGS from functools import partial class ManageDialog(Dialog): def __init__(self, controller, parent=None): super(ManageDialog, self).__init__(parent) self.setWindowTitle(m.manage_pin) # self.setFixedSize(480, 180) self._controller = controller self._build_ui() self._controller.on_found(self.refresh) self._controller.on_lost(self.accept) self._controller.use(self.refresh) def _build_ui(self): layout = QtGui.QVBoxLayout(self) layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) btns = QtGui.QHBoxLayout() self._pin_btn = QtGui.QPushButton(m.change_pin) self._pin_btn.clicked.connect(self._controller.wrap(self._change_pin, True)) btns.addWidget(self._pin_btn) self._puk_btn = QtGui.QPushButton(m.change_puk) self._puk_btn.clicked.connect(self._controller.wrap(self._change_puk, True)) self._key_btn = QtGui.QPushButton(m.change_key) self._key_btn.clicked.connect(self._controller.wrap(self._change_key, True)) if not settings.is_locked(SETTINGS.PIN_AS_KEY) or \ not settings[SETTINGS.PIN_AS_KEY]: btns.addWidget(self._puk_btn) btns.addWidget(self._key_btn) layout.addLayout(btns) self._messages = QtGui.QTextEdit() self._messages.setFixedSize(480, 100) self._messages.setReadOnly(True) layout.addWidget(self._messages) def refresh(self, controller): messages = [] if controller.pin_blocked: messages.append(IMPORTANT % m.pin_blocked) elif controller.does_pin_expire(): days_left = controller.get_pin_days_left() message = m.pin_days_left_1 % days_left if days_left < 7: message = IMPORTANT % message messages.append(message) if controller.pin_is_key: messages.append(m.pin_is_key) if controller.puk_blocked: messages.append(m.puk_blocked) if controller.pin_blocked: if controller.puk_blocked: self._pin_btn.setText(m.reset_device) else: self._pin_btn.setText(m.reset_pin) else: self._pin_btn.setText(m.change_pin) self._puk_btn.setDisabled(controller.puk_blocked) self._key_btn.setDisabled(controller.pin_is_key and controller.pin_blocked) self._messages.setHtml('
'.join(messages)) def _change_pin(self, controller, release): if controller.pin_blocked: if controller.puk_blocked: res = QtGui.QMessageBox.warning( self, m.reset_device, m.reset_device_warning, QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel) if res == QtGui.QMessageBox.Ok: worker = QtCore.QCoreApplication.instance().worker worker.post(m.resetting_device, controller.reset_device, partial(self._reset_callback, release), True) return else: dialog = ResetPinDialog(controller, self) else: dialog = SetPinDialog(controller, self) if dialog.exec_(): self.refresh(controller) def _change_puk(self, controller, release): dialog = SetPukDialog(controller, self) if dialog.exec_(): self.refresh(controller) def _change_key(self, controller, release): dialog = SetKeyDialog(controller, self) if dialog.exec_(): QtGui.QMessageBox.information(self, m.key_changed, m.key_changed_desc) self.refresh(controller) def _reset_callback(self, release, result): self.accept() QtGui.QMessageBox.information(self, m.device_resetted, m.device_resetted_desc) yubikey-piv-manager-1.3.0/pivman/view/set_key_dialog.py0000644000076500000240000001471312754337056023100 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui, QtCore from pivman import messages as m from pivman.piv import DeviceGoneError, PivError, KEY_LEN from pivman.view.utils import KEY_VALIDATOR from pivman.yubicommon import qt from binascii import b2a_hex import os class SetKeyDialog(qt.Dialog): def __init__(self, controller, parent=None): super(SetKeyDialog, self).__init__(parent) self._controller = controller self._build_ui() kt = self._kt_pin if self._controller.pin_is_key else self._kt_key kt.setChecked(True) self._change_key_type(kt) def _build_ui(self): self.setWindowTitle(m.change_key) self.setMinimumWidth(400) layout = QtGui.QVBoxLayout(self) self._current_key = QtGui.QLineEdit() self._current_key.setValidator(KEY_VALIDATOR) self._current_key.textChanged.connect(self._validate) if not self._controller.pin_is_key: layout.addWidget(QtGui.QLabel(m.current_key_label)) layout.addWidget(self._current_key) self._key_type = QtGui.QButtonGroup(self) self._kt_pin = QtGui.QRadioButton(m.use_pin_as_key, self) self._kt_key = QtGui.QRadioButton(m.use_separate_key, self) self._key_type.addButton(self._kt_pin) self._key_type.addButton(self._kt_key) self._key_type.buttonClicked.connect(self._change_key_type) layout.addWidget(self._kt_pin) layout.addWidget(self._kt_key) layout.addWidget(QtGui.QLabel(m.new_key_label)) self._key = QtGui.QLineEdit() self._key.setValidator(KEY_VALIDATOR) self._key.textChanged.connect(self._validate) layout.addWidget(self._key) buttons = QtGui.QDialogButtonBox() self._randomize_btn = QtGui.QPushButton(m.randomize) self._randomize_btn.clicked.connect(self.randomize) self._copy_btn = QtGui.QPushButton(m.copy_clipboard) self._copy_btn.clicked.connect(self._copy) buttons.addButton(self._randomize_btn, QtGui.QDialogButtonBox.ActionRole) buttons.addButton(self._copy_btn, QtGui.QDialogButtonBox.ActionRole) layout.addWidget(buttons) buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) self._ok_btn = buttons.button(QtGui.QDialogButtonBox.Ok) self._ok_btn.setDisabled(True) buttons.accepted.connect(self._set_key) buttons.rejected.connect(self.reject) layout.addWidget(buttons) @property def use_pin(self): return self._key_type.checkedButton() == self._kt_pin def _change_key_type(self, btn): if btn == self._kt_pin: self._key.setText('') self._key.setEnabled(False) self._randomize_btn.setEnabled(False) self._copy_btn.setEnabled(False) else: self.randomize() self._key.setEnabled(True) self._randomize_btn.setEnabled(True) self._copy_btn.setEnabled(True) self._validate() def randomize(self): self._key.setText(b2a_hex(os.urandom(KEY_LEN)).decode('ascii')) def _copy(self): self._key.selectAll() self._key.copy() self._key.deselect() def _validate(self): old_ok = self._controller.pin_is_key \ or self._current_key.hasAcceptableInput() new_ok = self.use_pin or self._key.hasAcceptableInput() self._copy_btn.setEnabled(not self.use_pin and new_ok) self._ok_btn.setEnabled(old_ok and new_ok) def _set_key(self): if self.use_pin and self._controller.pin_is_key: self.reject() return if not self._controller.puk_blocked and self.use_pin: res = QtGui.QMessageBox.warning(self, m.block_puk, m.block_puk_desc, QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel) if res != QtGui.QMessageBox.Ok: return try: if not self._controller.poll(): self._controller.reconnect() if self._controller.pin_is_key or self.use_pin: pin = self._controller.ensure_pin() else: pin = None current_key = pin \ if self._controller.pin_is_key else self._current_key.text() new_key = pin if self.use_pin else self._key.text() self._controller.ensure_authenticated(current_key) worker = QtCore.QCoreApplication.instance().worker worker.post(m.changing_key, (self._controller.set_authentication, new_key, self.use_pin), self._set_key_callback, True) except (DeviceGoneError, PivError, ValueError) as e: QtGui.QMessageBox.warning(self, m.error, str(e)) self.reject() def _set_key_callback(self, result): if isinstance(result, DeviceGoneError): QtGui.QMessageBox.warning(self, m.error, m.device_unplugged) self.accept() elif isinstance(result, Exception): QtGui.QMessageBox.warning(self, m.error, str(result)) else: self.accept() yubikey-piv-manager-1.3.0/pivman/view/set_pin_dialog.py0000644000076500000240000001440112752100505023051 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui, QtCore from pivman import messages as m from pivman.piv import WrongPinError from pivman.storage import settings, SETTINGS from pivman.utils import complexity_check from pivman.view.utils import pin_field from pivman.yubicommon import qt class SetPinDialog(qt.Dialog): window_title = m.change_pin label_current = m.current_pin_label label_new = m.new_pin_label label_verify = m.verify_pin_label warn_not_changed = m.pin_not_changed desc_not_changed = m.pin_not_changed_desc warn_not_complex = m.pin_not_complex busy = m.changing_pin info_changed = m.pin_changed desc_changed = m.pin_changed_desc def __init__(self, controller, parent=None, forced=False): super(SetPinDialog, self).__init__(parent) self._complex = settings[SETTINGS.COMPLEX_PINS] self._controller = controller self._build_ui(forced) def _build_ui(self, forced): self.setWindowTitle(self.window_title) layout = QtGui.QVBoxLayout(self) if forced: layout.addWidget(QtGui.QLabel(m.change_pin_forced_desc)) layout.addWidget(QtGui.QLabel(self.label_current)) self._old_pin = pin_field() layout.addWidget(self._old_pin) layout.addWidget(QtGui.QLabel(self.label_new)) self._new_pin = pin_field() layout.addWidget(self._new_pin) layout.addWidget(QtGui.QLabel(self.label_verify)) self._confirm_pin = pin_field() layout.addWidget(self._confirm_pin) self._new_pin.textChanged.connect(self._check_confirm) self._confirm_pin.textChanged.connect(self._check_confirm) if forced: buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok) else: buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) self._ok_btn = buttons.button(QtGui.QDialogButtonBox.Ok) self._ok_btn.setDisabled(True) buttons.accepted.connect(self._set_pin) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def _check_confirm(self): new_pin = self._new_pin.text() if len(new_pin) > 0 and new_pin == self._confirm_pin.text(): self._ok_btn.setDisabled(False) else: self._ok_btn.setDisabled(True) def _invalid_pin(self, title, reason): QtGui.QMessageBox.warning(self, title, reason) self._new_pin.setText('') self._confirm_pin.setText('') self._new_pin.setFocus() def _prepare_fn(self, old_pin, new_pin): self._controller.verify_pin(old_pin) if self._controller.does_pin_expire(): self._controller.ensure_authenticated(old_pin) return (self._controller.change_pin, old_pin, new_pin) def _set_pin(self): old_pin = self._old_pin.text() new_pin = self._new_pin.text() if old_pin == new_pin: self._invalid_pin(self.warn_not_changed, self.desc_not_changed) elif self._complex and not complexity_check(new_pin): self._invalid_pin(self.warn_not_complex, m.pin_complexity_desc) else: try: if not self._controller.poll(): self._controller.reconnect() fn = self._prepare_fn(old_pin, new_pin) worker = QtCore.QCoreApplication.instance().worker worker.post(self.busy, fn, self._change_pin_callback, True) except Exception as e: self._change_pin_callback(e) def _change_pin_callback(self, result): if isinstance(result, Exception): QtGui.QMessageBox.warning(self, m.error, str(result)) if isinstance(result, WrongPinError): self._old_pin.setText('') self._old_pin.setFocus() if result.blocked: self.accept() else: self.reject() else: self.accept() QtGui.QMessageBox.information(self, self.info_changed, self.desc_changed) class SetPukDialog(SetPinDialog): window_title = m.change_puk label_current = m.current_puk_label label_new = m.new_puk_label label_verify = m.verify_puk_label warn_not_changed = m.puk_not_changed desc_not_changed = m.puk_not_changed_desc warn_not_complex = m.puk_not_complex busy = m.changing_puk info_changed = m.puk_changed desc_changed = m.puk_changed_desc def _prepare_fn(self, old_puk, new_puk): return (self._controller.change_puk, old_puk, new_puk) class ResetPinDialog(SetPinDialog): window_title = m.reset_pin label_current = m.puk_label label_new = m.new_pin_label label_verify = m.verify_pin_label warn_not_changed = m.pin_puk_same desc_not_changed = m.pin_puk_same_desc warn_not_complex = m.pin_not_complex busy = m.changing_pin info_changed = m.pin_changed desc_changed = m.pin_changed_desc def _prepare_fn(self, puk, new_pin): return (self._controller.reset_pin, puk, new_pin) yubikey-piv-manager-1.3.0/pivman/view/settings_dialog.py0000644000076500000240000000706412754337056023276 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui from pivman import messages as m from pivman.yubicommon import qt from pivman.storage import settings, SETTINGS class SettingsDialog(qt.Dialog): def __init__(self, parent=None): super(SettingsDialog, self).__init__(parent) self.setWindowTitle(m.settings) self._build_ui() def _build_ui(self): layout = QtGui.QFormLayout(self) layout.addRow(self.section(m.pin)) self._complex_pins = QtGui.QCheckBox(m.use_complex_pins) self._complex_pins.setChecked(settings[SETTINGS.COMPLEX_PINS]) self._complex_pins.setDisabled( settings.is_locked(SETTINGS.COMPLEX_PINS)) layout.addRow(self._complex_pins) self._pin_expires = QtGui.QCheckBox(m.pin_expires) self._pin_expires_days = QtGui.QSpinBox() self._pin_expires_days.setMinimum(30) pin_expires = settings[SETTINGS.PIN_EXPIRATION] pin_expiry_locked = settings.is_locked(SETTINGS.PIN_EXPIRATION) self._pin_expires.setChecked(bool(pin_expires)) self._pin_expires_days.setValue(pin_expires) self._pin_expires.setDisabled(pin_expiry_locked) self._pin_expires_days.setDisabled( pin_expiry_locked or not pin_expires) self._pin_expires.stateChanged.connect( self._pin_expires_days.setEnabled) layout.addRow(self._pin_expires) layout.addRow(m.pin_expires_days, self._pin_expires_days) layout.addRow(self.section(m.misc)) reader_pattern = settings[SETTINGS.CARD_READER] self._reader_pattern = QtGui.QLineEdit(reader_pattern) layout.addRow(m.reader_name, self._reader_pattern) buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) buttons.accepted.connect(self._save) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def _pin_expires_changed(self, val): self._pin_expires_days.setEnabled(val) def _save(self): settings[SETTINGS.COMPLEX_PINS] = self._complex_pins.isChecked() settings[SETTINGS.CARD_READER] = self._reader_pattern.text() if self._pin_expires.isChecked(): settings[SETTINGS.PIN_EXPIRATION] = self._pin_expires_days.value() else: settings[SETTINGS.PIN_EXPIRATION] = 0 self.accept() yubikey-piv-manager-1.3.0/pivman/view/usage_policy_dialog.py0000644000076500000240000001056012752100505024075 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui from pivman import messages as m from pivman.storage import settings, SETTINGS from pivman.yubicommon import qt class UsagePolicyDialog(qt.Dialog): def __init__(self, controller, slot, parent=None): super(UsagePolicyDialog, self).__init__(parent) self._controller = controller self._slot = slot self.has_content = False self._build_ui() def _build_ui(self): self.setWindowTitle(m.usage_policy) self.setFixedWidth(400) layout = QtGui.QVBoxLayout(self) self._build_usage_policy(layout) buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def _build_usage_policy(self, layout): self._pin_policy = QtGui.QComboBox() self._pin_policy.addItem(m.pin_policy_default, None) self._pin_policy.addItem(m.pin_policy_never, 'never') self._pin_policy.addItem(m.pin_policy_once, 'once') self._pin_policy.addItem(m.pin_policy_always, 'always') self._touch_policy = QtGui.QCheckBox(m.touch_policy) if self._controller.version_tuple < (4, 0, 0): return use_pin_policy = self._slot in settings[SETTINGS.PIN_POLICY_SLOTS] use_touch_policy = self._slot in settings[SETTINGS.TOUCH_POLICY_SLOTS] if use_pin_policy or use_touch_policy: self.has_content = True layout.addWidget(self.section(m.usage_policy)) if use_pin_policy: pin_policy = settings[SETTINGS.PIN_POLICY] for index in range(self._pin_policy.count()): if self._pin_policy.itemData(index) == pin_policy: pin_policy_text = self._pin_policy.itemText(index) self._pin_policy.setCurrentIndex(index) break else: pin_policy = None pin_policy_text = m.pin_policy_default if settings.is_locked(SETTINGS.PIN_POLICY): layout.addWidget(QtGui.QLabel(m.pin_policy_1 % pin_policy_text)) else: pin_policy_box = QtGui.QHBoxLayout() pin_policy_box.addWidget(QtGui.QLabel(m.pin_policy)) pin_policy_box.addWidget(self._pin_policy) layout.addLayout(pin_policy_box) if use_touch_policy: self._touch_policy.setChecked(settings[SETTINGS.TOUCH_POLICY]) self._touch_policy.setDisabled( settings.is_locked(SETTINGS.TOUCH_POLICY)) layout.addWidget(self._touch_policy) @property def pin_policy(self): if settings.is_locked(SETTINGS.PIN_POLICY): return settings[SETTINGS.PIN_POLICY] return self._pin_policy.itemData(self._pin_policy.currentIndex()) @property def touch_policy(self): if self._controller.version_tuple < (4, 0, 0): return False if settings.is_locked(SETTINGS.TOUCH_POLICY): return settings[SETTINGS.TOUCH_POLICY] return self._touch_policy.isChecked() yubikey-piv-manager-1.3.0/pivman/view/utils.py0000644000076500000240000000602112754337056021247 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from PySide import QtGui, QtCore TOP_SECTION = '%s' SECTION = '
%s' IMPORTANT = '%s' PIN_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp(r'.{6,8}')) KEY_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp(r'[0-9a-fA-F]{48}')) SUBJECT_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp( r'^(/[a-zA-Z]+=[^/]+)+/?$')) class Dialog(QtGui.QDialog): def __init__(self, *args, **kwargs): super(Dialog, self).__init__(*args, **kwargs) self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint) self._headers = Headers() @property def headers(self): return self._headers def section(self, title): return self._headers.section(title) class Headers(object): def __init__(self): self._first = True def section(self, title): if self._first: self._first = False section = TOP_SECTION % title else: section = SECTION % title return QtGui.QLabel(section) def get_text(*args, **kwargs): flags = ( QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint ) kwargs['flags'] = flags return QtGui.QInputDialog.getText(*args, **kwargs) def pin_field(): field = QtGui.QLineEdit() field.setEchoMode(QtGui.QLineEdit.Password) field.setMaxLength(8) field.setValidator(PIN_VALIDATOR) return field def get_active_window(): active_win = QtGui.QApplication.activeWindow() if active_win is not None: return active_win wins = [w for w in QtGui.QApplication.topLevelWidgets() if isinstance(w, Dialog) and w.isVisible()] if not wins: return QtCore.QCoreApplication.instance().window return wins[0] # TODO: If more than one candidates remain, find best one. yubikey-piv-manager-1.3.0/pivman/watcher.py0000644000076500000240000000723512754337056020602 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from __future__ import print_function from PySide import QtGui, QtCore from pivman.controller import Controller from pivman.piv import YkPiv, PivError, DeviceGoneError from pivman.storage import settings, SETTINGS from functools import partial try: from Queue import Queue except ImportError: from queue import Queue class Release(object): def __init__(self, fn): self._fn = fn def __del__(self): self._fn() def __call__(self): raise Exception('EXPLICIT CALL') self._fn() self._fn = lambda: None class ControllerWatcher(QtCore.QObject): _device_found = QtCore.Signal() _device_lost = QtCore.Signal() def __init__(self): super(ControllerWatcher, self).__init__() self._waiters = Queue() self._controller = None self._lock = QtCore.QMutex() self._lock.lock() self._worker = QtCore.QCoreApplication.instance().worker self._worker.post_bg(self._poll, self._release, True) self.startTimer(2000) def timerEvent(self, event): if QtGui.QApplication.activeWindow() and self._lock.tryLock(): self._worker.post_bg(self._poll, self._release, True) event.accept() def _release(self, result=None): if self._controller and not self._waiters.empty(): waiter = self._waiters.get_nowait() waiter(self._controller, Release(self._release)) else: self._lock.unlock() def _poll(self): reader = settings[SETTINGS.CARD_READER] if self._controller: if self._controller.poll(): return self._controller = None self._device_lost.emit() try: self._controller = Controller(YkPiv(reader=reader)) self._device_found.emit() except (PivError, DeviceGoneError) as e: print(e) def on_found(self, fn, hold_lock=False): self._device_found.connect(self.wrap(fn, hold_lock)) def on_lost(self, fn): self._device_lost.connect(fn) def use(self, fn, hold_lock=False): if not hold_lock: def waiter(controller, release): fn(controller) else: waiter = fn if self._controller and self._lock.tryLock(): waiter(self._controller, Release(self._release)) else: self._waiters.put(waiter) def wrap(self, fn, hold_lock=False): return partial(self.use, fn, hold_lock) yubikey-piv-manager-1.3.0/pivman/yubicommon/0000755000076500000240000000000012755276415020747 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/pivman/yubicommon/__init__.py0000644000076500000240000000256212752100754023052 0ustar dagstaff00000000000000# 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. __version__ = '0.1.0' yubikey-piv-manager-1.3.0/pivman/yubicommon/compat.py0000644000076500000240000000145612752100754022577 0ustar dagstaff00000000000000"""Compatibility constants and helpers for Python 2.x and 3.x. """ import sys # NB If this module grows to more than a handful of items it is probably # to bite the bullet and depend on the six package. __all__ = [ 'string_types', 'binary_type', 'text_type', 'int2byte', 'byte2int' ] # Needed for isinstance() checks # Same behaviour as six.string_types https://pythonhosted.org/six/#constants if sys.version_info < (3, 0): # Python 2.x _PY2 = True string_types = (basestring,) binary_type = str text_type = unicode else: # Python 3.x _PY2 = False string_types = (str,) binary_type = bytes text_type = str def int2byte(i): if _PY2: return chr(i) return bytes((i,)) def byte2int(i): if _PY2: return ord(i) return i yubikey-piv-manager-1.3.0/pivman/yubicommon/ctypes/0000755000076500000240000000000012755276415022256 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/pivman/yubicommon/ctypes/__init__.py0000644000076500000240000000476612752100754024371 0ustar dagstaff00000000000000# 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 .libloader import load_library __all__ = ['load_library', 'use_library', 'CLibrary'] def use_library(libname, version=None): lib = load_library(libname, version) def define(func_name, argtypes, restype=None): try: f = getattr(lib, func_name) f.argtypes = argtypes f.restype = restype except AttributeError: print("Undefined symbol: %s" % func_name) def error(*args, **kwargs): raise Exception("Undefined symbol: %s" % func_name) f = error return f return define class CLibrary(object): """ Base class for extending to create python wrappers for c libraries. Example: class Foo(CLibrary): foo_func = [c_bool, c_char_p], int foo = Foo('libfoo') assert foo.foo_func(True, 'Hello!') == 7 """ def __init__(self, libname, version=None): self._lib = use_library(libname, version) def __getattribute__(self, name): val = object.__getattribute__(self, name) if isinstance(val, tuple) and len(val) == 2: return self._lib(name, *val) return val yubikey-piv-manager-1.3.0/pivman/yubicommon/ctypes/libloader.py0000644000076500000240000002542012752100754024555 0ustar dagstaff00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2008 David James # Copyright (c) 2006-2008 Alex Holkner # 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. # * Neither the name of pyglet nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # 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. # ---------------------------------------------------------------------------- from __future__ import absolute_import import os.path import re import sys import glob import platform import ctypes import ctypes.util def _environ_path(name): if name in os.environ: return os.environ[name].split(":") else: return [] class LibraryLoader(object): def __init__(self): self.other_dirs = [] def load_library(self, libname, version=None): """Given the name of a library, load it.""" paths = self.getpaths(libname) for path in paths: if os.path.exists(path): return self.load(path) raise ImportError("%s not found." % libname) def load(self, path): """Given a path to a library, load it.""" try: # Darwin requires dlopen to be called with mode RTLD_GLOBAL instead # of the default RTLD_LOCAL. Without this, you end up with # libraries not being loadable, resulting in "Symbol not found" # errors if sys.platform == 'darwin': return ctypes.CDLL(path, ctypes.RTLD_GLOBAL) else: return ctypes.cdll.LoadLibrary(path) except OSError as e: raise ImportError(e) def getpaths(self, libname): """Return a list of paths where the library might be found.""" if os.path.isabs(libname): yield libname else: # FIXME / TODO return '.' and os.path.dirname(__file__) for path in self.getplatformpaths(libname): yield path path = ctypes.util.find_library(libname) if path: yield path def getplatformpaths(self, libname): return [] # Darwin (Mac OS X) class DarwinLibraryLoader(LibraryLoader): name_formats = ["lib%s.dylib", "lib%s.so", "lib%s.bundle", "%s.dylib", "%s.so", "%s.bundle", "%s"] def getplatformpaths(self, libname): if os.path.pathsep in libname: names = [libname] else: names = [format % libname for format in self.name_formats] for dir in self.getdirs(libname): for name in names: yield os.path.join(dir, name) def getdirs(self, libname): '''Implements the dylib search as specified in Apple documentation: http://developer.apple.com/documentation/DeveloperTools/Conceptual/ DynamicLibraries/Articles/DynamicLibraryUsageGuidelines.html Before commencing the standard search, the method first checks the bundle's ``Frameworks`` directory if the application is running within a bundle (OS X .app). ''' dyld_fallback_library_path = _environ_path( "DYLD_FALLBACK_LIBRARY_PATH") if not dyld_fallback_library_path: dyld_fallback_library_path = [os.path.expanduser('~/lib'), '/usr/local/lib', '/usr/lib'] dirs = [] if '/' in libname: dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) else: dirs.extend(_environ_path("LD_LIBRARY_PATH")) dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) dirs.extend(self.other_dirs) dirs.append(".") dirs.append(os.path.dirname(__file__)) if hasattr(sys, 'frozen') and sys.frozen == 'macosx_app': dirs.append(os.path.join( os.environ['RESOURCEPATH'], '..', 'Frameworks')) if hasattr(sys, 'frozen'): dirs.append(sys._MEIPASS) dirs.extend(dyld_fallback_library_path) return dirs # Posix class PosixLibraryLoader(LibraryLoader): _ld_so_cache = None def load_library(self, libname, version=None): try: return self.load(ctypes.util.find_library(libname)) except ImportError: return super(PosixLibraryLoader, self).load_library( libname, version) def _create_ld_so_cache(self): # Recreate search path followed by ld.so. This is going to be # slow to build, and incorrect (ld.so uses ld.so.cache, which may # not be up-to-date). Used only as fallback for distros without # /sbin/ldconfig. # # We assume the DT_RPATH and DT_RUNPATH binary sections are omitted. directories = [] for name in ("LD_LIBRARY_PATH", "SHLIB_PATH", # HPUX "LIBPATH", # OS/2, AIX "LIBRARY_PATH", # BE/OS ): if name in os.environ: directories.extend(os.environ[name].split(os.pathsep)) directories.extend(self.other_dirs) directories.append(".") directories.append(os.path.dirname(__file__)) try: directories.extend([dir.strip() for dir in open('/etc/ld.so.conf')]) except IOError: pass unix_lib_dirs_list = ['/lib', '/usr/lib', '/lib64', '/usr/lib64'] if sys.platform.startswith('linux'): # Try and support multiarch work in Ubuntu # https://wiki.ubuntu.com/MultiarchSpec bitage = platform.architecture()[0] if bitage.startswith('32'): # Assume Intel/AMD x86 compat unix_lib_dirs_list += [ '/lib/i386-linux-gnu', '/usr/lib/i386-linux-gnu'] elif bitage.startswith('64'): # Assume Intel/AMD x86 compat unix_lib_dirs_list += [ '/lib/x86_64-linux-gnu', '/usr/lib/x86_64-linux-gnu'] else: # guess... unix_lib_dirs_list += glob.glob('/lib/*linux-gnu') directories.extend(unix_lib_dirs_list) cache = {} lib_re = re.compile(r'lib(.*)\.s[ol]') ext_re = re.compile(r'\.s[ol]$') for dir in directories: try: for path in glob.glob("%s/*.s[ol]*" % dir): file = os.path.basename(path) # Index by filename if file not in cache: cache[file] = path # Index by library name match = lib_re.match(file) if match: library = match.group(1) if library not in cache: cache[library] = path except OSError: pass self._ld_so_cache = cache def getplatformpaths(self, libname): if self._ld_so_cache is None: self._create_ld_so_cache() result = self._ld_so_cache.get(libname) if result: yield result path = ctypes.util.find_library(libname) if path: yield os.path.join("/lib", path) # Windows class _WindowsLibrary(object): def __init__(self, path): self.cdll = ctypes.cdll.LoadLibrary(path) self.windll = ctypes.windll.LoadLibrary(path) def __getattr__(self, name): try: return getattr(self.cdll, name) except AttributeError: try: return getattr(self.windll, name) except AttributeError: raise class WindowsLibraryLoader(LibraryLoader): name_formats = ["%s.dll", "lib%s.dll", "%slib.dll"] def load_library(self, libname, version=None): try: result = LibraryLoader.load_library(self, libname, version) except ImportError: result = None if os.path.sep not in libname: formats = self.name_formats[:] if version: formats.append("lib%%s-%s.dll" % version) for name in formats: try: result = getattr(ctypes.cdll, name % libname) if result: break except WindowsError: result = None if result is None: try: result = getattr(ctypes.cdll, libname) except WindowsError: result = None if result is None: raise ImportError("%s not found." % libname) return result def load(self, path): return _WindowsLibrary(path) def getplatformpaths(self, libname): if os.path.sep not in libname: for name in self.name_formats: dll_in_current_dir = os.path.abspath(name % libname) if os.path.exists(dll_in_current_dir): yield dll_in_current_dir path = ctypes.util.find_library(name % libname) if path: yield path # Platform switching # If your value of sys.platform does not appear in this dict, please contact # the Ctypesgen maintainers. loaderclass = { "darwin": DarwinLibraryLoader, "cygwin": WindowsLibraryLoader, "win32": WindowsLibraryLoader } loader = loaderclass.get(sys.platform, PosixLibraryLoader)() def add_library_search_dirs(other_dirs): loader.other_dirs = other_dirs load_library = loader.load_library del loaderclass yubikey-piv-manager-1.3.0/pivman/yubicommon/qt/0000755000076500000240000000000012755276415021373 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/pivman/yubicommon/qt/__init__.py0000644000076500000240000000474412752100754023502 0ustar dagstaff00000000000000# 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 __future__ import absolute_import from PySide import QtGui from .utils import * from .classes import * from .worker import * from .settings import * import sys import traceback # Font fixes for OSX if sys.platform == 'darwin': from platform import mac_ver mac_version = tuple(int(x) for x in mac_ver()[0].split('.')) if (10, 9) <= mac_version < (10, 10): # Mavericks QtGui.QFont.insertSubstitution('.Lucida Grande UI', 'Lucida Grande') if (10, 10) <= mac_version < (10, 11): # Yosemite QtGui.QFont.insertSubstitution('.Helvetica Neue DeskInterface', 'Helvetica Neue') if (10, 11) <= mac_version: #El Capitan QtGui.QFont.insertSubstitution('.SF NS Text', 'Helvetica Neue') # Replace excepthook with one that releases the exception to prevent memory # leaks: def excepthook(typ, val, tback): try: traceback.print_exception(typ, val, tback) sys.exc_clear() del sys.last_value del sys.last_traceback del sys.last_type except: pass # Ignore failure here, we're likely shutting down... sys.excepthook = excepthook yubikey-piv-manager-1.3.0/pivman/yubicommon/qt/classes.py0000644000076500000240000001341712752100754023375 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from __future__ import absolute_import from PySide import QtGui, QtCore from .worker import Worker import os import sys import importlib from .. import compat __all__ = ['Application', 'Dialog', 'MutexLocker'] TOP_SECTION = '%s' SECTION = '
%s' class Dialog(QtGui.QDialog): def __init__(self, *args, **kwargs): super(Dialog, self).__init__(*args, **kwargs) self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint) self._headers = _Headers() @property def headers(self): return self._headers def section(self, title): return self._headers.section(title) class _Headers(object): def __init__(self): self._first = True def section(self, title): if self._first: self._first = False section = TOP_SECTION % title else: section = SECTION % title return QtGui.QLabel(section) class _MainWindow(QtGui.QMainWindow): def __init__(self): super(_MainWindow, self).__init__() self._widget = None def hide(self): if sys.platform == 'darwin': from .osx import app_services app_services.osx_hide() else: super(_MainWindow, self).hide() def customEvent(self, event): event.callback() event.accept() class Application(QtGui.QApplication): _quit = False def __init__(self, m=None, version=None): super(Application, self).__init__(sys.argv) self._determine_basedir() self._read_package_version(version) self.window = _MainWindow() if m: # Run all strings through Qt translation for key in dir(m): if (isinstance(key, compat.string_types) and not key.startswith('_')): setattr(m, key, self.tr(getattr(m, key))) self.worker = Worker(self.window, m) def _determine_basedir(self): if getattr(sys, 'frozen', False): # we are running in a PyInstaller bundle self.basedir = sys._MEIPASS else: # we are running in a normal Python environment top_module_str = __package__.split('.')[0] top_module = importlib.import_module(top_module_str) self.basedir = os.path.dirname(top_module.__file__) def _read_package_version(self, version): if version is None: return pversion_fn = os.path.join(self.basedir, 'package_version.txt') try: with open(pversion_fn, 'r') as f: pversion = int(f.read().strip()) except: pversion = 0 if pversion > 0: version += '.%d' % pversion self.version = version def ensure_singleton(self, name=None): if not name: name = self.applicationName() from PySide import QtNetwork self._l_socket = QtNetwork.QLocalSocket() self._l_socket.connectToServer(name, QtCore.QIODevice.WriteOnly) if self._l_socket.waitForConnected(): self._stop() sys.exit(0) else: self._l_server = QtNetwork.QLocalServer() if not self._l_server.listen(name): QtNetwork.QLocalServer.removeServer(name) self._l_server.listen(name) self._l_server.newConnection.connect(self._show_window) def _show_window(self): self.window.show() self.window.activateWindow() def quit(self): super(Application, self).quit() self._quit = True def _stop(self): worker_thread = self.worker.thread() worker_thread.quit() worker_thread.wait() self.deleteLater() sys.stdout.flush() sys.stderr.flush() def exec_(self): if not self._quit: status = super(Application, self).exec_() else: status = 0 self._stop() return status class MutexLocker(object): """Drop-in replacement for QMutexLocker that can start unlocked.""" def __init__(self, mutex, lock=True): self._mutex = mutex self._locked = False if lock: self.relock() def lock(self, try_lock=False): if try_lock: self._locked = self._mutex.tryLock() else: self._mutex.lock() self._locked = True return self._locked and self or None def relock(self): self.lock() def unlock(self): if self._locked: self._mutex.unlock() def __del__(self): self.unlock() yubikey-piv-manager-1.3.0/pivman/yubicommon/qt/osx.py0000644000076500000240000000364012752100754022546 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. import ctypes from ..ctypes import CLibrary __all__ = ['app_services'] class ProcessSerialNumber(ctypes.Structure): _fields_ = [('highLongOfPsn', ctypes.c_uint32), ('lowLongOfPSN', ctypes.c_uint32)] class ApplicationServices(CLibrary): ShowHideProcess = [ctypes.POINTER(ProcessSerialNumber), ctypes.c_bool], None GetFrontProcess = [ctypes.POINTER(ProcessSerialNumber)], None def osx_hide(self): """ Hide the window and let the dock icon be able to show the window again. """ psn = ProcessSerialNumber() self.GetFrontProcess(ctypes.byref(psn)) self.ShowHideProcess(ctypes.byref(psn), False) app_services = ApplicationServices('ApplicationServices') yubikey-piv-manager-1.3.0/pivman/yubicommon/qt/settings.py0000644000076500000240000001042112752100754023570 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from __future__ import absolute_import from PySide import QtCore from collections import MutableMapping __all__ = ['Settings', 'PySettings', 'convert_to'] def convert_to(value, target_type): if target_type is list: return [] if value is None else [value] if target_type is int: return 0 if value in ['', 'false', 'False'] else int(value) if target_type is float: return float(value) if target_type is bool: return value not in ['', 'false', 'False'] return value class SettingsGroup(object): def __init__(self, settings, mutex, group): self._settings = settings self._mutex = mutex self._group = group def __getattr__(self, method_name): if hasattr(self._settings, method_name): fn = getattr(self._settings, method_name) def wrapped(*args, **kwargs): try: self._mutex.lock() self._settings.beginGroup(self._group) return fn(*args, **kwargs) finally: self._settings.endGroup() self._mutex.unlock() return wrapped def rename(self, new_name): data = dict((key, self.value(key)) for key in self.childKeys()) self.remove('') self._group = new_name for k, v in data.items(): self.setValue(k, v) def __repr__(self): return 'Group(%s)' % self._group class Settings(QtCore.QObject): def __init__(self, q_settings, wrap=True): super(Settings, self).__init__() self._mutex = QtCore.QMutex(QtCore.QMutex.Recursive) self._wrap = wrap self._q_settings = q_settings def get_group(self, group): g = SettingsGroup(self._q_settings, self._mutex, group) if self._wrap: g = PySettings(g) return g @staticmethod def wrap(*args, **kwargs): return Settings(QtCore.QSettings(*args, **kwargs)) class PySettings(MutableMapping): def __init__(self, settings): self._settings = settings def __getattr__(self, method_name): return getattr(self._settings, method_name) def get(self, key, default=None): val = self._settings.value(key, default) if not isinstance(val, type(default)): val = convert_to(val, type(default)) return val def __getitem__(self, key): return self.get(key) def __setitem__(self, key, value): self._settings.setValue(key, value) def __delitem__(self, key): self._settings.remove(key) def __iter__(self): for key in list(self.keys()): yield key def __len__(self): return len(self._settings.childKeys()) def __contains__(self, key): return self._settings.contains(key) def keys(self): return self._settings.childKeys() def update(self, data): for key, value in list(data.items()): self[key] = value def clear(self): self._settings.remove('') def __repr__(self): return 'PySettings(%s)' % self._settings yubikey-piv-manager-1.3.0/pivman/yubicommon/qt/utils.py0000644000076500000240000000700712753046477023112 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from __future__ import absolute_import from PySide import QtCore, QtGui from functools import wraps from inspect import getargspec __all__ = ['get_text', 'get_active_window', 'is_minimized', 'connect_once'] class _DefaultMessages(object): def __init__(self, default_m, m=None): self._defaults = default_m self._m = m def __getattr__(self, method_name): if hasattr(self._m, method_name): return getattr(self._m, method_name) else: return getattr(self._defaults, method_name) def default_messages(_m, name='m'): def inner(fn): @wraps(fn) def wrapper(*args, **kwargs): index = getargspec(fn).args.index(name) if len(args) > index: args = list(args) args[index] = _DefaultMessages(_m, args[index]) else: kwargs[name] = _DefaultMessages(_m, kwargs.get(name)) return fn(*args, **kwargs) return wrapper return inner def get_text(*args, **kwargs): flags = ( QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint ) kwargs['flags'] = flags return QtGui.QInputDialog.getText(*args, **kwargs) def get_active_window(): active_win = QtGui.QApplication.activeWindow() if active_win is not None: return active_win wins = [w for w in QtGui.QApplication.topLevelWidgets() if isinstance(w, QtGui.QDialog) and w.isVisible()] if not wins: return QtCore.QCoreApplication.instance().window return wins[0] # TODO: If more than one candidates remain, find best one. def connect_once(signal, slot): _SignalConnector(signal, slot) def is_minimized(window): """Returns True iff the window is minimized or has been sent to the tray""" return not window.isVisible() or window.isMinimized() class _SignalConnector(QtCore.QObject): _instances = set() def __init__(self, signal, slot): super(_SignalConnector, self).__init__() self.signal = signal self.slot = slot self._instances.add(self) self.signal.connect(self.wrappedSlot) def wrappedSlot(self, *args, **kwargs): self._instances.discard(self) self.signal.disconnect(self.wrappedSlot) self.slot(*args, **kwargs) yubikey-piv-manager-1.3.0/pivman/yubicommon/qt/worker.py0000644000076500000240000000712012753046540023246 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from __future__ import absolute_import from PySide import QtGui, QtCore from functools import partial from os import getenv from .utils import connect_once, get_active_window, default_messages import traceback class _Messages(object): wait = 'Please wait...' class _Event(QtCore.QEvent): EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType()) def __init__(self, callback): super(_Event, self).__init__(_Event.EVENT_TYPE) self._callback = callback def callback(self): self._callback() del self._callback class Worker(QtCore.QObject): _work_signal = QtCore.Signal(tuple) _work_done_0 = QtCore.Signal() @default_messages(_Messages) def __init__(self, window, m): super(Worker, self).__init__() self.m = m self.window = window self._work_signal.connect(self.work) self.work_thread = QtCore.QThread() self.moveToThread(self.work_thread) self.work_thread.start() def post(self, title, fn, callback=None, return_errors=False): busy = QtGui.QProgressDialog(title, None, 0, 0, get_active_window()) busy.setWindowTitle(self.m.wait) busy.setWindowModality(QtCore.Qt.WindowModal) busy.setMinimumDuration(0) busy.setWindowFlags( busy.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint) busy.show() connect_once(self._work_done_0, busy.close) self.post_bg(fn, callback, return_errors) def post_bg(self, fn, callback=None, return_errors=False): if isinstance(fn, tuple): fn = partial(fn[0], *fn[1:]) self._work_signal.emit((fn, callback, return_errors)) def post_fg(self, fn): if isinstance(fn, tuple): fn = partial(fn[0], *fn[1:]) event = _Event(fn) QtGui.QApplication.postEvent(self.window, event) @QtCore.Slot(tuple) def work(self, job): QtCore.QThread.msleep(10) # Needed to yield (fn, callback, return_errors) = job try: result = fn() except Exception as e: result = e if getenv('DEBUG'): traceback.print_exc() if not return_errors: def callback(e): raise e if callback: event = _Event(partial(callback, result)) QtGui.QApplication.postEvent(self.window, event) self._work_done_0.emit() yubikey-piv-manager-1.3.0/pivman/yubicommon/setup/0000755000076500000240000000000012755276415022107 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/pivman/yubicommon/setup/__init__.py0000644000076500000240000002161512752100754024212 0ustar dagstaff00000000000000# 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 __future__ import absolute_import __dependencies__ = [] __all__ = ['get_version', 'setup', 'release'] from setuptools import setup as _setup, find_packages, Command from setuptools.command.sdist import sdist from distutils import log from distutils.errors import DistutilsSetupError from datetime import date from glob import glob import os import re VERSION_PATTERN = re.compile(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$") DEPENDENCY_PATTERN = re.compile( r"(?m)__dependencies__\s*=\s*\[((['\"].+['\"]\s*(,\s*)?)+)\]") YC_DEPENDENCY_PATTERN = re.compile( r"(?m)__yc_dependencies__\s*=\s*\[((['\"].+['\"]\s*(,\s*)?)+)\]") base_module = __name__.rsplit('.', 1)[0] def get_version(module_name_or_file=None): """Return the current version as defined by the given module/file.""" if module_name_or_file is None: parts = base_module.split('.') module_name_or_file = parts[0] if len(parts) > 1 else \ find_packages(exclude=['test', 'test.*'])[0] if os.path.isdir(module_name_or_file): module_name_or_file = os.path.join(module_name_or_file, '__init__.py') with open(module_name_or_file, 'r') as f: match = VERSION_PATTERN.search(f.read()) return match.group(1) def get_dependencies(module): basedir = os.path.dirname(__file__) fn = os.path.join(basedir, module + '.py') if os.path.isfile(fn): with open(fn, 'r') as f: match = DEPENDENCY_PATTERN.search(f.read()) if match: return [s.strip().strip('"\'') for s in match.group(1).split(',')] return [] def get_yc_dependencies(module): basedir = os.path.dirname(__file__) fn = os.path.join(basedir, module + '.py') if os.path.isfile(fn): with open(fn, 'r') as f: match = YC_DEPENDENCY_PATTERN.search(f.read()) if match: return [s.strip().strip('"\'') for s in match.group(1).split(',')] return [] def get_package(module): return base_module + '.' + module def setup(**kwargs): # TODO: Find a better way to pass this to a command. os.environ['setup_long_name'] = kwargs.pop('long_name', kwargs.get('name')) if 'version' not in kwargs: kwargs['version'] = get_version() packages = kwargs.setdefault( 'packages', find_packages(exclude=['test', 'test.*', base_module + '.*'])) packages.append(__name__) install_requires = kwargs.setdefault('install_requires', []) yc_blacklist = kwargs.pop('yc_requires_exclude', []) yc_requires = kwargs.pop('yc_requires', []) yc_requires_stack = yc_requires[:] while yc_requires_stack: yc_module = yc_requires_stack.pop() for yc_dep in get_yc_dependencies(yc_module): if yc_dep not in yc_requires: yc_requires.append(yc_dep) yc_requires_stack.append(yc_dep) for yc_module in yc_requires: packages.append(get_package(yc_module)) for dep in get_dependencies(yc_module): if dep not in install_requires and dep not in yc_blacklist: install_requires.append(dep) cmdclass = kwargs.setdefault('cmdclass', {}) cmdclass.setdefault('release', release) cmdclass.setdefault('build_man', build_man) cmdclass.setdefault('sdist', custom_sdist) return _setup(**kwargs) class custom_sdist(sdist): def run(self): self.run_command('build_man') # Run if available: if 'qt_resources' in self.distribution.cmdclass: self.run_command('qt_resources') sdist.run(self) class build_man(Command): description = "create man pages from asciidoc source" user_options = [] boolean_options = [] def initialize_options(self): pass 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 run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") for fname in glob(os.path.join('man', '*.adoc')): self.announce("Converting: " + fname, log.INFO) self.execute(os.system, ('a2x -d manpage -f manpage "%s"' % fname,)) 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 _verify_not_dirty(self): if os.system('git diff --shortstat | grep -q "."') == 0: raise DistutilsSetupError("Git has uncommitted changes!") 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 run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") self._verify_version() self._verify_tag() self._verify_not_dirty() self.run_command('check') self.execute(os.system, ('git2cl > ChangeLog',)) self.run_command('sdist') if not self.skip_tests: try: self.run_command('test') except SystemExit as e: if e.code != 0: raise DistutilsSetupError("There were test failures!") 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.announce("Release complete! Don't forget to:", log.INFO) self.announce("") self.announce(" git push && git push --tags", log.INFO) self.announce("") yubikey-piv-manager-1.3.0/pivman/yubicommon/setup/exe.py0000644000076500000240000000563312752100754023236 0ustar dagstaff00000000000000# 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 __future__ import absolute_import from setuptools import Command from distutils.errors import DistutilsSetupError import os import json import tempfile class executable(Command): description = "create an executable" user_options = [ ('debug', None, "build with debug flag"), ('data-files=', None, "data files to include"), ('package-version=', None, "package version") ] boolean_options = ['debug'] def initialize_options(self): self.debug = 0 self.data_files = '' self.package_version = '0' def finalize_options(self): self.cwd = os.getcwd() self.data_files = self.data_files.split() self.package_version = int(self.package_version) def run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") from PyInstaller.__main__ import run as pyinst_run os.environ['pyinstaller_data'] = json.dumps({ 'debug': self.debug, 'name': self.distribution.get_name(), 'long_name': os.environ['setup_long_name'], 'data_files': self.data_files, 'package_version': self.package_version }) spec = tempfile.NamedTemporaryFile(suffix='.spec', delete=False) source = os.path.join(os.path.dirname(__file__), 'pyinstaller_spec.py') with open(source) as f: spec.write(f.read()) spec_name = spec.name spec.close() pyinst_run([spec_name]) os.unlink(spec_name) self.announce("Executable created!") yubikey-piv-manager-1.3.0/pivman/yubicommon/setup/pyinstaller_spec.py0000644000076500000240000001344712752100754026037 0ustar dagstaff00000000000000# -*- mode: python -*- # -*- encoding: utf-8 -*- from __future__ import absolute_import from __future__ import print_function import os import sys import json import errno import pkg_resources from glob import glob VS_VERSION_INFO = """ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four # items: (1, 2, 3, 4) # Set not needed items to zero 0. filevers=%(ver_tup)r, prodvers=%(ver_tup)r, # Contains a bitmask that specifies the valid bits 'flags'r mask=0x0, # Contains a bitmask that specifies the Boolean attributes # of the file. flags=0x0, # The operating system for which this file was designed. # 0x4 - NT and there is no need to change it. OS=0x4, # The general type of file. # 0x1 - the file is an application. fileType=0x1, # The function of the file. # 0x0 - the function is not defined for this fileType subtype=0x0, # Creation date and time stamp. date=(0, 0) ), kids=[ StringFileInfo( [ StringTable( u'040904E4', [StringStruct(u'FileDescription', u'%(name)s'), StringStruct(u'FileVersion', u'%(ver_str)s'), StringStruct(u'InternalName', u'%(internal_name)s'), StringStruct(u'LegalCopyright', u'Copyright © 2015 Yubico'), StringStruct(u'OriginalFilename', u'%(exe_name)s'), StringStruct(u'ProductName', u'%(name)s'), StringStruct(u'ProductVersion', u'%(ver_str)s')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1252])]) ] )""" data = json.loads(os.environ['pyinstaller_data']) try: data = dict((k, v.encode('ascii') if getattr(v, 'encode', None) else v) for k, v in data.items()) except NameError: pass # Python 3, encode not needed. dist = pkg_resources.get_distribution(data['name']) DEBUG = bool(data['debug']) NAME = data['long_name'] WIN = sys.platform in ['win32', 'cygwin'] OSX = sys.platform in ['darwin'] ver_str = dist.version if data['package_version'] > 0: ver_str += '.%d' % data['package_version'] file_ext = '.exe' if WIN else '' if WIN: icon_ext = 'ico' elif OSX: icon_ext = 'icns' else: icon_ext = 'png' ICON = os.path.join('resources', '%s.%s' % (data['name'], icon_ext)) if not os.path.isfile(ICON): ICON = None # Generate scripts from entry_points. merge = [] entry_map = dist.get_entry_map() console_scripts = entry_map.get('console_scripts', {}) gui_scripts = entry_map.get('gui_scripts', {}) for ep in list(gui_scripts.values()) + list(console_scripts.values()): script_path = os.path.join(os.getcwd(), ep.name + '-script.py') with open(script_path, 'w') as fh: fh.write("import %s\n" % ep.module_name) fh.write("%s.%s()\n" % (ep.module_name, '.'.join(ep.attrs))) merge.append( (Analysis([script_path], [dist.location], None, None, None, None), ep.name, ep.name + file_ext) ) MERGE(*merge) # Read version information on Windows. VERSION = None if WIN: VERSION = 'build/file_version_info.txt' global int_or_zero # Needed due to how this script is invoked def int_or_zero(v): try: return int(v) except ValueError: return 0 ver_tup = tuple(int_or_zero(v) for v in ver_str.split('.')) # Windows needs 4-tuple. if len(ver_tup) < 4: ver_tup += (0,) * (4-len(ver_tup)) elif len(ver_tup) > 4: ver_tup = ver_tup[:4] # Write version info. with open(VERSION, 'w') as f: f.write(VS_VERSION_INFO % { 'name': NAME, 'internal_name': data['name'], 'ver_tup': ver_tup, 'ver_str': ver_str, 'exe_name': data['name'] + file_ext }) pyzs = [PYZ(m[0].pure) for m in merge] exes = [] for (a, a_name, a_name_ext), pyz in zip(merge, pyzs): exe = EXE(pyz, a.scripts, exclude_binaries=True, name=a_name_ext, debug=DEBUG, strip=None, upx=True, console=DEBUG or a_name in console_scripts, append_pkg=not OSX, version=VERSION, icon=ICON) exes.append(exe) # Sign the executable if WIN: os.system("signtool.exe sign /fd SHA256 /t http://timestamp.verisign.com/scripts/timstamp.dll \"%s\"" % (exe.name)) collect = [] for (a, _, a_name), exe in zip(merge, exes): collect += [exe, a.binaries, a.zipfiles, a.datas] # Data files collect.append([(os.path.basename(fn), fn, 'DATA') for fn in data['data_files']]) # DLLs, dylibs and executables should go here. collect.append([(fn[4:], fn, 'BINARY') for fn in glob('lib/*')]) coll = COLLECT(*collect, strip=None, upx=True, name=NAME) # Write package version for app to display pversion_fn = os.path.join('dist', NAME, 'package_version.txt') with open(pversion_fn, 'w') as f: f.write(str(data['package_version'])) # Create .app for OSX if OSX: app = BUNDLE(coll, name="%s.app" % NAME, version=ver_str, icon=ICON) qt_conf = 'dist/%s.app/Contents/Resources/qt.conf' % NAME qt_conf_dir = os.path.dirname(qt_conf) try: os.makedirs(qt_conf_dir) except OSError as e: if not (e.errno == errno.EEXIST and os.path.isdir(qt_conf_dir)): raise with open(qt_conf, 'w') as f: f.write('[Path]\nPlugins = plugins') # Create Windows installer if WIN: installer_cfg = 'resources/win-installer.nsi' if os.path.isfile(installer_cfg): os.system('makensis.exe -D"VERSION=%s" %s' % (ver_str, installer_cfg)) installer = "dist/%s-%s-win.exe" % (data['name'], ver_str) os.system("signtool.exe sign /fd SHA256 /t http://timestamp.verisign.com/scripts/timstamp.dll \"%s\"" % (installer)) print("Installer created: %s" % installer) yubikey-piv-manager-1.3.0/pivman/yubicommon/setup/qt.py0000644000076500000240000000573612752100754023105 0ustar dagstaff00000000000000# 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 __future__ import absolute_import from setuptools import Command from distutils.errors import DistutilsSetupError import os __dependencies__ = ['PySide'] __yc_dependencies__ = ['ctypes'] __all__ = ['qt_resources'] class _qt_resources(Command): description = "convert file resources into code" user_options = [] boolean_options = [] _source = 'qt_resources' _target = '' def initialize_options(self): pass def finalize_options(self): self.cwd = os.getcwd() self.source = os.path.join(self.cwd, self._source) self.target = os.path.join(self.cwd, self._target) def _create_qrc(self): qrc = os.path.join(self.source, 'qt_resources.qrc') with open(qrc, 'w') as f: f.write('\n\n') for fname in os.listdir(self.source): f.write('%s\n' % fname) f.write('\n\n') return qrc def run(self): if os.getcwd() != self.cwd: raise DistutilsSetupError("Must be in package root!") qrc = self._create_qrc() self.execute(os.system, ('pyside-rcc -py3 "%s" -o "%s"' % (qrc, self.target),)) os.unlink(qrc) self.announce("QT resources compiled into %s" % self.target) def qt_resources(target, sourcedir='qt_resources'): target = target.replace('.', os.path.sep) if os.path.isdir(target): target = os.path.join(target, 'qt_resources.py') else: target += '.py' return type('qt_resources', (_qt_resources, object), { '_source': sourcedir, '_target': target }) yubikey-piv-manager-1.3.0/PKG-INFO0000644000076500000240000000131312755276415016367 0ustar dagstaff00000000000000Metadata-Version: 1.1 Name: yubikey-piv-manager Version: 1.3.0 Summary: Tool for configuring your PIV-enabled YubiKey. Home-page: https://github.com/Yubico/yubikey-piv-manager Author: Yubico Open Source Maintainers Author-email: ossmaint@yubico.com License: GPLv3+ Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: X11 Applications :: Qt Classifier: Intended Audience :: End Users/Desktop Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Utilities yubikey-piv-manager-1.3.0/qt_resources/0000755000076500000240000000000012755276415020012 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/qt_resources/pivman.png0000644000076500000240000000452212752100505021774 0ustar dagstaff00000000000000PNG  IHDR00WbKGD pHYs  tIME  9/oiTXtCommentCreated with GIMPd.eIDAThŚmlSڱB^L')I/I ːhP4QiӴ:WUUT!2HIU"iUWib뺩liFX 8$,$!'}ه< ׎S{sssKP`lI_0bZSʁ%3 V u k@&/;끃nal~*~%;?%p 9w^(ˑ 717əJ"ӊkhh&R~I$9%/}C;h # ݅UGeQ#e.y31nĕb kOvM,LW4jSu伴 w½'?H<ّʔY{>odxMWI6v5BCvײ>?vȒ-U$}9?*mWn`׏*N^=pJG7pwID9m>:G_2UMW8|$i= L'~hU^6_Vb΍%;kCbS/mDdU=?E9tմMKWYPxYz],x .|&*5%iox6A4mzFK#qw+lzde-x1䃽x )|?gӁt y56-8)݊f|QVdhݒ6Y& D ĕ/`{ǒLw;e1%Cb.UPQvfNFU_0F]xxCILIwAI?PX3hШ,j̸>滨zܐw#77$NA,jDj! 89e.O ԕ5SڰK6Nb-t&g,*ŀ]u0z ƽw hsl=„Έdr$ 6 egpB)sPrC@`ocx8+EN{!BX:u#9bNX mUNE'fidN]i3vّ1=Z-~QE ,acXdFg]ysڳSt)}2kSlbo%G`l&`QWj9'([06}*YYLC2Ýpo zzq]lz.k}#^bʴW>o`NN7ɌAp\ nURoYU&8Ü:cūv'V.A*Fx'1i{=q Y_Q* t.: (Tb$Gؙ}2!:uҭ8S~hj &,;*K::7;MjXnK[^)NMa"f̷zpɊS4W-kCvDV"6!(ec8tw.;F"}(1u*aֺ۲Ug']CƂ7SqJy~(G'JGƀ'6 z}.K6v5Lc-Y(F?TEV`hyN޷GEa9Qd8Sap/f4/J?җS7?b*] k$5Eb)Ӌ`+T;‰J/ \LچKV9,`ƾ ǀHpF?αoGx#{DDS0!p8|%$8R,֧1[0x sU.W\怿)(\?FJx]IENDB`yubikey-piv-manager-1.3.0/README0000644000076500000240000000124012754536251016145 0ustar dagstaff00000000000000== YubiKey PIV Manager image:https://travis-ci.org/Yubico/yubikey-piv-manager.svg?branch=master["Build Status", link="https://travis-ci.org/Yubico/yubikey-piv-manager"] Graphical application for configuring a PIV-enabled YubiKey. image::screenshot.png[] === Installation The recommended way to install this software including dependencies is by using the provided precompiled binaries for your platform. For Windows and OS X (10.7 and above), there are installers available for download https://developers.yubico.com/yubikey-piv-manager/Releases/[here]. For Ubuntu we have a custom PPA with a package for it https://launchpad.net/~yubico/+archive/ubuntu/stable[here]. yubikey-piv-manager-1.3.0/resources/0000755000076500000240000000000012755276415017306 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/resources/installer_bg.png0000644000076500000240000000041012752100505022433 0ustar dagstaff00000000000000PNG  IHDRU`bKGD pHYs  tIME  #-.yOIDATx nH@/QIENDB`yubikey-piv-manager-1.3.0/resources/osx-installer.pkgproj0000644000076500000240000005272312752100505023500 0ustar dagstaff00000000000000 PACKAGES PACKAGE_FILES DEFAULT_INSTALL_LOCATION / HIERARCHY CHILDREN CHILDREN CHILDREN GID 80 PATH Utilities PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH ../dist/YubiKey PIV Manager.app PATH_TYPE 1 PERMISSIONS 493 TYPE 3 UID 0 GID 80 PATH Applications PATH_TYPE 0 PERMISSIONS 509 TYPE 1 UID 0 CHILDREN CHILDREN GID 80 PATH Application Support PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Automator PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Documentation PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Filesystems PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Frameworks PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Input Methods PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Internet Plug-Ins PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchAgents PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchDaemons PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PreferencePanes PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Preferences PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 80 PATH Printers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PrivilegedHelperTools PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickLook PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickTime PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Screen Savers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Scripts PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Services PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Widgets PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH Library PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN CHILDREN GID 0 PATH Extensions PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH Library PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH System PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN GID 0 PATH Shared PATH_TYPE 0 PERMISSIONS 1023 TYPE 1 UID 0 GID 80 PATH Users PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH / PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 PAYLOAD_TYPE 0 VERSION 3 PACKAGE_SCRIPTS RESOURCES PACKAGE_SETTINGS AUTHENTICATION 1 CONCLUSION_ACTION 0 IDENTIFIER com.yubico.pkg.YubiKeyPIVManager NAME YubiKey PIV Manager OVERWRITE_PERMISSIONS VERSION 1.0 UUID C979A9E1-2859-4C59-AB6D-139EC31EDA52 PROJECT PROJECT_COMMENTS NOTES PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjExMzguNTEiPgo8c3R5bGUg dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 Pgo8L2JvZHk+CjwvaHRtbD4K PROJECT_PRESENTATION BACKGROUND ALIGNMENT 0 BACKGROUND_PATH PATH installer_bg.png PATH_TYPE 1 CUSTOM 1 SCALING 0 INSTALLATION_STEPS ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewIntroductionController INSTALLER_PLUGIN Introduction LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewReadMeController INSTALLER_PLUGIN ReadMe LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewLicenseController INSTALLER_PLUGIN License LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewDestinationSelectController INSTALLER_PLUGIN TargetSelect LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationTypeController INSTALLER_PLUGIN PackageSelection LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationController INSTALLER_PLUGIN Install LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewSummaryController INSTALLER_PLUGIN Summary LIST_TITLE_KEY InstallerSectionTitle INTRODUCTION LOCALIZATIONS LICENSE KEYWORDS LOCALIZATIONS MODE 0 README LOCALIZATIONS TITLE LOCALIZATIONS LANGUAGE English VALUE YubiKey PIV Manager PROJECT_REQUIREMENTS LIST POSTINSTALL_PATH PREINSTALL_PATH RESOURCES ROOT_VOLUME_ONLY PROJECT_SETTINGS ADVANCED_OPTIONS BUILD_FORMAT 0 BUILD_PATH PATH ../dist PATH_TYPE 1 EXCLUDED_FILES PATTERNS_ARRAY REGULAR_EXPRESSION STRING .DS_Store TYPE 0 PROTECTED PROXY_NAME Remove .DS_Store files PROXY_TOOLTIP Remove ".DS_Store" files created by the Finder. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING .pbdevelopment TYPE 0 PROTECTED PROXY_NAME Remove .pbdevelopment files PROXY_TOOLTIP Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING CVS TYPE 1 REGULAR_EXPRESSION STRING .cvsignore TYPE 0 REGULAR_EXPRESSION STRING .cvspass TYPE 0 REGULAR_EXPRESSION STRING .svn TYPE 1 REGULAR_EXPRESSION STRING .git TYPE 1 REGULAR_EXPRESSION STRING .gitignore TYPE 0 PROTECTED PROXY_NAME Remove SCM metadata PROXY_TOOLTIP Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING classes.nib TYPE 0 REGULAR_EXPRESSION STRING designable.db TYPE 0 REGULAR_EXPRESSION STRING info.nib TYPE 0 PROTECTED PROXY_NAME Optimize nib files PROXY_TOOLTIP Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING Resources Disabled TYPE 1 PROTECTED PROXY_NAME Remove Resources Disabled folders PROXY_TOOLTIP Remove "Resources Disabled" folders. STATE SEPARATOR NAME YubiKey PIV Manager TYPE 0 VERSION 2 yubikey-piv-manager-1.3.0/resources/pivman.desktop0000644000076500000240000000037512752100505022157 0ustar dagstaff00000000000000[Desktop Entry] Name=YubiKey PIV Manager GenericName=YubiKey PIV Manager Comment=Tool for configuring your PIV-enabled YubiKey Exec=pivman Icon=pivman StartupNotify=false Terminal=false Type=Application Categories=Utility; Keywords=YubiKey;PIV;Manager; yubikey-piv-manager-1.3.0/resources/pivman.xpm0000644000076500000240000000475012752100505021313 0ustar dagstaff00000000000000/* XPM */ static char * neoman_xpm[] = { "32 32 81 1", " c None", ". c #8CC041", "+ c #8BC040", "@ c #89BE3C", "# c #88BE3A", "$ c #8CC040", "% c #87BD39", "& c #A9D071", "* c #CCE3AA", "= c #E1EFCD", "- c #EEF6E2", "; c #E1EECE", "> c #CCE4AB", ", c #88BE3B", "' c #C1DD98", ") c #FAFDF7", "! c #FFFFFF", "~ c #FAFCF8", "{ c #C0DD97", "] c #98C755", "^ c #F3F8EA", "/ c #F2F8EA", "( c #A3CC66", "_ c #A2CD66", ": c #FBFDF8", "< c #F1F8E8", "[ c #F1F8E9", "} c #F8FBF4", "| c #FAFCF7", "1 c #F9FCF6", "2 c #82BA30", "3 c #A1CC63", "4 c #B2D580", "5 c #F6FAEF", "6 c #AED378", "7 c #86BD37", "8 c #8ABF3E", "9 c #AAD173", "0 c #C4DF9E", "a c #DAEBC2", "b c #8ABF3D", "c c #E0EECB", "d c #92C44B", "e c #94C44E", "f c #ACD276", "g c #92C34A", "h c #AAD072", "i c #E6F1D5", "j c #88BE39", "k c #C8E1A4", "l c #CCE3AB", "m c #F7FBF2", "n c #86BD38", "o c #B6D787", "p c #D5E8B9", "q c #8BBF3F", "r c #E1EFCE", "s c #8EC145", "t c #A7CF6E", "u c #AFD37B", "v c #EDF5E1", "w c #DCECC4", "x c #E3F0D1", "y c #96C551", "z c #E2EFCE", "A c #C0DC97", "B c #87BE39", "C c #FEFEFC", "D c #E2EFD0", "E c #B4D683", "F c #AAD073", "G c #E8F2D9", "H c #C0DD96", "I c #F6FAF0", "J c #9AC858", "K c #B8D98A", "L c #85BD36", "M c #84BC34", "N c #CFE5B0", "O c #98C756", "P c #FAFCF6", " ", " .......... ", " .............. ", " ......+@##@+...... ", " ....$%&*=--;>&%$.... ", " ....,')!!!!!!!!~{,.... ", " ....]^!!!!!!!!!!!!/].... ", " ....(!!!!!!!!!!!!!!!!_.... ", " ...]!!:<[[}!!!!|<[<|!!].... ", " ...,^!!12%%3!!!!4%%25!!^#... ", " ..$'!!!!6..75!!!8..9!!!!{$.. ", " ...%)!!!!=@.$0!!ab.@c!!!!:%... ", " ...&!!!!!!d..e!!f..g!!!!!!h... ", " ..+*!!!!!!0$.,i!j.+k!!!!!!l+.. ", " ..@=!!!!!!mn..opq.%:!!!!!!r@.. ", " ..#-!!!!!!!&..st..u!!!!!!!v#.. ", " ..#-!!!!!!!wb..+.,x!!!!!!!v#.. ", " ..@;!!!!!!!!s....y!!!!!!!!z@.. ", " ..+>!!!!!!!!A...ql!!!!!!!!l+.. ", " ...&!!!!!!!!^B..#C!!!!!!!!h... ", " ...%~!!!!!!!D@..E!!!!!!!!:%... ", " ..${!!!!!!!F..#G!!!!!!!!H$.. ", " ...,/!!!!!In..J!!!!!!!!/,... ", " ...]!!!!!KL7MN!!!!!!!!O.... ", " ...._!!!!PPPP!!!!!!!!_.... ", " ....]^!!!!!!!!!!!!/O.... ", " ....#{:!!!!!!!!:H,.... ", " ....$%hlrvvzlh%$.... ", " ......+@##@+...... ", " ................ ", " .......... ", " "}; yubikey-piv-manager-1.3.0/resources/win-installer.nsi0000644000076500000240000000731312752100505022574 0ustar dagstaff00000000000000!include "MUI2.nsh" !define MUI_ICON "yubikey-piv-manager.ico" ; The name of the installer Name "YubiKey PIV Manager" ; The file to write OutFile "../dist/yubikey-piv-manager-${VERSION}-win.exe" ; The default installation directory InstallDir "$PROGRAMFILES\Yubico\YubiKey PIV Manager" ; Registry key to check for directory (so if you install again, it will ; overwrite the old one automatically) InstallDirRegKey HKLM "Software\Yubico\YubiKey PIV Manager" "Install_Dir" SetCompressor /SOLID lzma ShowInstDetails show Var MUI_TEMP Var STARTMENU_FOLDER ;Interface Settings !define MUI_ABORTWARNING ;-------------------------------- ; Pages !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_DIRECTORY ;Start Menu Folder Page Configuration !define MUI_STARTMENUPAGE_DEFAULTFOLDER "Yubico\YubiKey PIV Manager" !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Yubico\YubiKey PIV Manager" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ;Languages !insertmacro MUI_LANGUAGE "English" ;-------------------------------- Section "YubiKey PIV Manager" ; Remove all DELETE "$INSTDIR\*" SectionIn RO SetOutPath $INSTDIR FILE "..\dist\YubiKey PIV Manager\*" SectionEnd Var MYTMP # Last section is a hidden one. Section WriteUninstaller "$INSTDIR\uninstall.exe" ; Write the installation path into the registry WriteRegStr HKLM "Software\Yubico\YubiKey PIV Manager" "Install_Dir" "$INSTDIR" # Windows Add/Remove Programs support StrCpy $MYTMP "Software\Microsoft\Windows\CurrentVersion\Uninstall\YubiKey PIV Manager" WriteRegStr HKLM $MYTMP "DisplayName" "YubiKey PIV Manager" WriteRegExpandStr HKLM $MYTMP "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegExpandStr HKLM $MYTMP "InstallLocation" "$INSTDIR" WriteRegStr HKLM $MYTMP "DisplayVersion" "${VERSION}" WriteRegStr HKLM $MYTMP "Publisher" "Yubico AB" WriteRegStr HKLM $MYTMP "URLInfoAbout" "http://www.yubico.com" WriteRegDWORD HKLM $MYTMP "NoModify" "1" WriteRegDWORD HKLM $MYTMP "NoRepair" "1" !insertmacro MUI_STARTMENU_WRITE_BEGIN Application ;Create shortcuts SetShellVarContext all SetOutPath "$SMPROGRAMS\$STARTMENU_FOLDER" CreateShortCut "YubiKey PIV Manager.lnk" "$INSTDIR\pivman.exe" "" "$INSTDIR\pivman.exe" 0 CreateShortCut "Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 1 !insertmacro MUI_STARTMENU_WRITE_END CreateShortCut "$SMSTARTUP\YubiKey PIV Manager PIN-check.lnk" "$INSTDIR\pivman.exe" "-c" SectionEnd ; Uninstaller Section "Uninstall" ; Remove registry keys DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\pivman" DeleteRegKey HKLM "Software\Yubico\YubiKey PIV Manager" ; Remove all DELETE "$INSTDIR\*" ; Remove shortcuts, if any !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP SetShellVarContext all Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" Delete "$SMPROGRAMS\$MUI_TEMP\YubiKey PIV Manager.lnk" Delete "$SMSTARTUP\YubiKey PIV Manager PIN-check.lnk" ;Delete empty start menu parent diretories StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" startMenuDeleteLoop: ClearErrors RMDir $MUI_TEMP GetFullPathName $MUI_TEMP "$MUI_TEMP\.." IfErrors startMenuDeleteLoopDone StrCmp $MUI_TEMP $SMPROGRAMS startMenuDeleteLoopDone startMenuDeleteLoop startMenuDeleteLoopDone: DeleteRegKey /ifempty HKCU "Software\Yubico\YubiKey PIV Manager" ; Remove directories used RMDir "$INSTDIR" SectionEnd yubikey-piv-manager-1.3.0/resources/yubikey-piv-manager.icns0000644000076500000240000011664212752100505024042 0ustar dagstaff00000000000000icnsic08 jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2d#Creator: JasPer Version 1.900.1R \@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP Y߂XSqJfu)+1dL!簷*UM_jȩz kj~]>abo?9pUSɢw1ܻ߂8SqJf_iոP%æ/\w*Bɀq! ˖4RlwhE߂PSqJfХصΫ^5ȲG-WQow b8&WqU$+iVy#k+Paٙ1FHᴏ߂0(W+'BZYM _/_˖A@N̂xMNhoj ˕[o~ 7ssq\+@ކI;S Ki<8Vo!*%'Obl(7H|s2-HM"qCr-}촕ۀz<+-I{j{?"yQ0q ,En0CIhcS^]UQǢ$ZҫzVYC!1HL%s~q 6|R_lʮ~ A@>ͨ'yOog_ ~P@Y3LȲjvr_ਇixEffеWA5U*16$ xW;cD΂f[7zTY֛3Ol)_q`iF=-j:ԠFP|τȃEr{D>"(H?qV(Scv*z2+Tw}i#F.Q y`RO-7' |IKdCjyѥʫ9ua͟Ҋ6)U> O*mn 9D\8'yZS:>B'\ 6M żh*M hՁK 8IH9+T"tYa9Z6RM65MU`AH M2} w ؛)nlwr0,5N ՟dui\p2NX`&z %ll.qR4ZV,^ (\OXb@Z+*T^g-|`*#]ܻh"4m+пKK6k.)ag6@*{ ˃%`Z@UECԇ[je@z9L,d=ɔjfK=yY@iý~1Jm%VswyID;YyiVN' 0mڕosW,jhJIfl+u]K"j!9/9]%caP3Gx]Np>>=<^v:,W@[ΗFwKQe,L5 V.#?\np~הK@7*73bU#'8>-U_3TI4sA"TN-aQ si*[+rs8߾!3>M)?DZ8jE؆C͡aZg,wp$bx5#Y6k)2N JBީOؚF2g MyջgJ{ڇ8烕;uK>Ng֣8)KTM3! /#GʖFmݟ:ˣOf2Y4L8 `˂;<~I!DI5mfBӆ#BH~*X$ZpӞ~<bǥ mGZt!kfb[΍̸+W % +L;Dž1VQ?xTT} 賤$YnvVዣ hh-~5,(Z~E|ÕLi˴7f+вKFx[kMqq8b̢~3/޿ksTk80cjDrw'yhnld1+Ir&v2|ZtM>,FCéc:ηK|AUb86e$32hWt|,U  쿚f?Mm}zyeqW%ȁсmEW۰JWq4@ *ێӉmào>JH] 9oe>?-w4&0^fVrfI ߋy Uc[?;pm,~~Bخ:VȨegSś;u45=+Lz 0(e!ۮܑTy%&*YiW E8:z[{(3LCm^teVͮ%96Vn >[,6O/SYDTT0iB 7nPj!5Z8炆rU :#_`W=?˝aW(Q[*=M%95ynd‹I03?KJu9XtdEi&FEX m<[[Z`јާ4 >&)0l!OIV9}RvNTtK14+"gώNvA7O?>nfSzXq=^gk?[ּ<~S#U]ٜwɯNH!T&d>Wɖ10&V:ciY(#'%^,0xtf I%i=wSV %?t#>vO4#1nl}"46%d)>D~3xm,T<.? lPԺܵ;-hn.J=(wz'FuEꐙi$i-K[s`k!u|`l4N!_ǏP>~ϳݵB`{"ۈJ젠˕/Ms"abKce u][K#"x*K?V*FLP /S?.0r %v|"|,UJ n"!7eUM>dUuEԌi  X=zϸ-h ѱ)p$!\ b՘-VE;σYtDNo&B(H0rw4H VN.i'~$[ڙY0P׼qJZ( |zb#G Fg~r =SϊUuL&D'vy sA  [*2AT!hxjy"vI nJgb{`6zouKdžh tŭw‚܉_Z^Z)oEay:N\Zxt:@!1hZ^cGdXa# Bε9GuC'@A SIEX?,:V,kY P%)IvFY?àt @V< |qe 'ژ;˘#T6i3)dԴ$2wf](K*%"D,(BuI<+0iyKÍXwmuS: " &08 eqD:RQ̬FdM4~{GuJ`^SҚ6}Ėwܤrvi2xG+~8ډ5}^&}DBFy \Iub@`JV aa`% `Œ)}`.' E^ҁ fjͻ{Af L_l ܤpjid4QuPp#=gxk{3Ec7 IAчjBcuZlώo&uGfVG@^! #v|/y[c)$3t(sP3R+rCn_ w-l,іb3 a47Vgi B۸e?>]_3#{,WoJS~due>9,OѨÓĻ(ʔ]~ZR-i._mȗBЩK8wotC-3Pyi !XB&մ۽_m B.t &cI%$`Qn@gr|LBJ< 2Q\!pYh33vմm ]!S-ujS3GѮs\8"ONf"6XmM<ZgwK/q֙yModx #[HIH) ߛel!k$=7L0#҈0rUd1rmBVp4oPpa_t0P8JA_tҋZElg҈\&:~:6>$'̆dfn.yJ-zsM%Kk3HO2HRj۝"{ry-I6apDΫI1ƠDeY4SEHHJA.%džaEYUֿ5gYUi"Ϟ7hb&\*O0ɄobPւ#u_<7ڛe3B9deۚTf>XԮ{AA`ӽ`~2L4!Wel |}} k^uZw^9-Fk/33Cz&&k&hr.׭Xݯ/3Dbֵ王/) ӭI @d)P|'d8Q~o EAtП/ߝ~z̀N!~E|8= >W9îw`f<C% HHhl1JY`5p)C'!5]ƉDrLLcfx=8ve2AeUf}g/2QBz,O;ftM(}s7S4;ETn0V;> zQuګ5 7@w+tYEֹًP#Gq4|1=(cqyt-%HgSx!,zH k^Ui>Y"qa-Kd'OtGP3uwA޳bqI%>/9`@6>~ pC3@2 ktm)IGIѰ,x2t YRvc48';mt8hB=_c_{]!K|S^`I??IsÿStTM CZ>|onkHWAmG4;hnm⅄#iP+s.Ӓ5Mitt%YYNW탿z6V ?ˉ$g_۶rxFWq`dy_ztT i]VkĚk_>~"Wفvl WRc-FZo˔noQql\#ŝoDVf:CR'@O3Ounm,!3ĕlrmhйgol~`RE# 8[*I<ņ |i ] xӝ͇o2wDߚ ^d%*ꃿï_&<~)慸Zgk8;EDn,ic ,Q[ta$Q_/ >\$"-L7.88PASa HZYG&.x` ^Eh\2&~9ԷUk = jrr}$D﯈,4DSr:0^ {@ye@|c5\}^!Õ8)=qϒ4*kF=sGD,S*m#GHTlfWU'AmZE6dݽͷ/LmH,#^g!nZNC1኷>@6X9arUQ;Đ3+<ԺI &K+s>mdJz1{AylMٱ 2!YĦTGӫ!,F}\m6̶e_MkV]i+)KfSzaSxxܛlñadM ?cT?!GvΆ & ՉvJQmkwJLJ5)2`8mig`OVЋWyS/Hp؁wsT9+Pڎ8Go]*WT`{,'p`SgV7vZs m_V@'fMEbLӍtvp58ɿIGOnGՋy˼&`8XAA#~#By nC,}D FQ~,ҩq-3LhEaǐ}Y9ᒷE6hZRt ,7e+zi,zcN,/R,3XA|(f8V 35!=HS2첉Xp^顃|J_5<%?bSIRu 0o x{y6N)oFm$+KQ{ r0E _:W MRڟy&p dP8 (fj@R|c҄h䆟a0R *iw٠V۪`pJGIEq@B<b/U2R+vK^I)k6huP[k`y32ZCߒ  ]O{mi?@W>wxη|3!\gG=EQ\G ._rCNTLƿ^dVNz=}W; >5H$ 5eZF b V#HOrGZEړOW`U@7FK"% 7r/AS86:T^mܞnLϴAِ)Raj#6I6Iǣs_ѺViMg@q#`bHn81q q64󌆑M϶aV,w<RM=DnV'NUچ9Q4nF4@+fxz_Άځr[{ O[9lCeMҹ-ށQ܌ډԆ i<\;TMU{eMor!Lj-J5@џ́=c(4a8TPW-R7s$8qZOd !c6aH+OP`v;sW)!UfKMqY1vQ }#wmD d^P!7@q`NJr:6Ay}u#'&)lʬ>;QP'%af:8qIb?QyBzA_]݂nEVg {𴫶)ݚ\N{nj~:h7khk8xkOҡv|vW`)=:N"M!9ix+߁=xu SX)uګ? nDg(!I&^۸-"{øZV|S%8PkCNQ}˜e gWeE0"}N6 )x(wƘ, FI`6s;{<օRб/[H.\q)֛OhՆTIw 3;AIv+-%^cq`{1{G1eSsAeAI9R8icJR ? u36m\흃(ST/}Q[uS`rO[=ht `rы;X3;\.p$䯷N86i[m٬'S}bH~Q<У\>]+ǏAj@5M!vYɊ >1%)j~ rz Œ5o4~,2TݚW+N֤p(u[iٯHh;ovaH%OӬ.XDХ(AqdAo=å.ci₮ M5no5d_i[w 9ejǂ7Iѿ~ Bީ XnZW=&}M9F8Os%-+|>Z0Nr@K\ZAqKcf*߁P[pqP)zifH‡ZydH{C.V;hTz [%IɦY's}rUd _y,eE[{#%6'Y,f7hj2)YYMїJ\͠1G`Y_I/ z2`]oח|?(c{Exbm|~VI?X= *ڼ -ۛ7NGIhq9M`@6klβdY405p_d*ak.N9 E^Pr`>u9аo4s[`M,N+O8)C6$m'AZ:kc䶿ݽl}c",MW P^2"PdV_YU f)Z!c1]y~I\ aJ,FKF ssQ:?踃3𖐵vm#EhvO{SD4  oI-< |fl24u8YSD-{ie2./` w=U0¡^PʓZ> sV nERBTonw)4_U?1He8:Q&SWy5P=I=)PŽ A ezv1e=663{qv!_Ə/x ,ƄTw'ďfsDD&ϼM]&zGe"᧏ |O-Q)ӓZx"'%AH^X_t%kP?{2&eN|_uHƜBčmw-FqGX4gO\gQ]%z*Yv/Q$A=TO2ƤE MJOf\5"dð #3e7k@j&*8eWhAUӴw=} ޠ^ޒ `P-?{꥙J/Um, Pc_;Q/Yx 4h_?YgAfn=|]J"C_wjJv \gMPG &ম=+S,U4cU,y`T5[8y%'MAYEH]“ 5 qzѦ~Ք:} }P19q?ͽoIB1_Fmy I?E.k3 B>b1J0;zflk}UOu&d_WQ&>m6AMCZY楛lӶ-EyjS,>ةcrZ>uh,|?E?E!~Yhs4=ј1͐: N ڍW%YnS%noXa} /N9e_\gN֧;u0ⶊ2z VE ?y֝4S UTx 7E9&E8 6?ZQ(NT"PLJw/{lHIHn!psd屓FxXٲ^q x̓"xP*]z FEW&Xq6 z&_%r*x#v\ԝclWQWD/(Ipu5ܵDY ِckN4["ƻ P* {Hsx6M[2QI#x4YX RO_q]= }!d|Ԃ340'brwL6-D` nIh=Aa`ŗkވC"?FeyZ𭊟EGEFz8!ð[! WJ:9YR_hFӰfMY9l4 nDp(sհj!bv&l Dte˰*`VqUCGWe3ZzLA )C*&/r0ЮIN,v.?F4Zc^c E(EUN͠RtI@I+d4xPv=xLͱ'3@Cҽi֩\e/i@aIĜ+1V0 hCwҌ0YC0&(9Fm[r ^m#πvm8-DHagkS'&r_XcE2VC}wYr@!n)m]vT\YBd^7ۍ/[:֘/V)6 p_Z m=Oz.%x#˩/'ӎ{KsZ I8+/Y}/_,r񽎱u+H O"AL57TE%oGsꗲȂhDIFiЙ[&_DSFw^mAMXE(%jԘ!ص׹WSU"4վ4ZT(RI{O]1f^d/'zIdap nT[RNo< r'Y6àG'::skǐ {"ƯKv0Hۀ~Uab`~[rvc ~W13?9VP3a<Չ ƒ@;1Qb%baS|Kڊ5ԶNЬUE#o' X`s3c,{ptcĆx>2wɓ*de' `kϓ?6eհWBY2:"+w1毐>բ62QHxGSA6+ (|4+TeӰ{Loui2HgXTa΁0IoNVm冔Y,>ݛN21yg@cv>EF9W9"ǼD.ǴRPgіK3ѾH2G(Kv"^MDgߦpң^ӳ&bT3eX6cƣPV_ =i1_ξo^4T_(pl}tY*!+;9=: `$njq.D`:pc)6a#:E .oհdobG)$50a`xxc͘f~_ ˓h6LF} s k49qwќ&-X|`$Nn9KODb+HQ, $n'O: ߳Ro\\sȷ-}S{mаAF>K3Q-2cy'2Ӆšr7ᵳ j =e2x,5g|1wCJ5ۯ㶂wYy̓P;BTqy%52f)O:6yԣj>:vB^e-@úZ4Sg]+C4졯Taԗ$HCǙL>&B,x8mȑ!r;'xC؊1?>+Jx;6'gBӌ <. M2t4=H7f,OTƂ7n|ŤH%X؋}<&ˡk!t:҄D?>Bb /O.x 3IBH[ ǀsAs- VF[NU1̨ @%uO NOgl?Hŋ"YP JlzW z8 3wF 6,zKz,U00N2i_ ~aG$[ILa;V檦F~^D`hl"$q-YR/{AcjOl:ۛ,)&>.z !xC>W/bD?{!b~@&hĽ{= ratH?,TJ ĮB_ҧ:W?B[D^G+3!SnOW?.pBj4[LUҋoR};Cd?Ct|&uiM wINwi$HL'y'jj~WOh*~<X`Q=R 4 4⛾VnѺoNX.T')*kWd1cmB)F0Q=""^W!d p "0YPcàt!юq&.@B$3oެ.L4V:p#X(kٍ2SunRR?,M*qV-݀g-¸WP6zNc2<5x7g*٘de2=HGGD}p ҅?YH5#9>ai`OSAAM7k.D+Wׇ7Ե-- )yhNRs+up\5]K9" 3:hx7kG؇mhHT5=[oe:Ց{'e3!dssF8s*#*8oꝦ&~E/+ln02Mԥ{Ëk=gL }jm:IF;N)9,HF˔4 @&Cn?jm2a~ '؆ڱЙXn6}b0,]fq!,wB}t1 ~7 \)%jagL[mzŲH ]/s⬒7v֛FufO׊rˑ?p?cIG`B0ސUM%=T,72_M{%KM>꾦*$s׽tnpλ:bX8g8 ("ٔ$~b.ϰ+5Ӫ~!%SiE-ID%!H y*ubqK3a0.3 AUE}-GX=(zA wXyQ8Q( !VaJpGrgm?I9XZЏsSgZzgWCJ(oP2E'bOHBzō]%Cχ,yeeL+λM |N]`nF! GfDxaQ >@IRVO)SBHaf+ ^C*<;~-NqtE>? G:tڈ, S6eBxH1ST,Y{azpg( rC%'ZNz.O'k[Z?&С5\=fH_^,9s"9BC;УJVf@>E_Xn,Z~xaSa>t*%>*x?M2q@J٧8ݦ (t}J(TuNJ*[B/.{R!1rcF􂁮#ԌRosg`/HwDp\5bd_Nl}gm,iT +жFVeoHmX܋a!j+3.(AmD$ܠܤs<X?BwZwLޠYd>x@Tifl|fScxENsSHހeN@&(Q EQEE?E:-7/_xt7wC{߀D6L+v5b^W|e{$lGth7XZGÀd_uY\3Ԥ#9gI:̜O%%ej |(F 5`]~,_: Dzbi)8 %Y6] c}_AU+R2G;3# LݏNQyNf z2oS6_ 3KS4maJPTidŽ~89J` Ϝ6gy -|,]kifŋ￀`seP|Of+yDX߶͙^ML;uRfQ?AKa#@no^/;,[;3`#r :nX}^H.K$LPk%/HϮ,m#؝U24} ^ZQ㶡D:(;"3ѿX`Cv1*|XLO&2_٧&V!0\̴XVz0,QSػѫG.Ib\I_Ч'q!2&;2֫ ;yYA*un!睤~yT{h]ҕ MvPo '9#gr!y.u>ƝC'bkp{Gx|."]AVʜz$+X^l%\s7M刯2UN%DɢLM<"gz/n8yL{#/~Ss7[bϹlb黇Vu~5EDnIS>$+5lԇf ;Uh|#wQԬg dom ^aM.kàRvT ~1Ѿ"M@ijXz_)~qQdg NC< YZ>Yv*<$ 2fB ΁4Q?y҂'~rb,W$;ˮje>z$2nнyvB;vry*$9T1K/ǖA\mȹ|m/*ʚp0jq ƃ㙠>/k|ʢy~4>-օ'@lQ{N6=%S+(ǟɀڌpZ˂"9HC}(@jόwfa JlRCTv'1hz$rbʃVIc3{Dͳ'#"򼏰}]yA"pIp"R)8"3h mgcx>S$etS9o_"<A/Yc@< AO܊4s4@f7AŮ5pl6(pk25?exEI(]3PN1@~&@"'A7B/BEiy3?AroFm[ I OicBPӟ 5baW'`  p>87 ?'ͥ N㱄 )x j_K $u7C}pQ5`^!ݟZٮ96y%D 0[>a1~hjEQ Hڡ(NP7+\VZD\tʈ2Z ff6|:Q-㙏^s.Aȕb#Qs2<*@sr808Z:@n\g7\>muZx&H[fU}iL+g,/SE$ݮ8q8e8{p9bcn 4e3ڄJVf9>W+lXJo`z8pjYn~!M([ĉcGs: i{1?x 9-oJZMLlLVV42Mo:ÑF|ϋw K xFꙛL:_B$f *4<״}GmU֘_ڶo{&ZǧĀe6NMk*KްΎ .C%K6/<67tn!1dzʦ>Ko-{ r6s׫N>":y4mcS`B><_dfJKPy]ZߝgZq7gN‚77e 8('2R;K2/G\NhJ3܃?pz:.|N HyZz3vI[^o\Al;nHXahi[۾$UQnC^C9XlgE@B̦p? r# ^S^ E˞DMe['o&rz+خteqUXG&)|j>~/Tc#q\V_-x$ rFǶM~?+%*<#*є/ zCַ͟?Nt#Hk-HeU_Ud8@k=w=%c?5LCv&gЇz29U`gALj ߢ,Q@T *ጇ{=+$cAlnA Y 7',p̟Iԃ&q ==Q=+]74<%^ikg 7nsNW:ۨ{gp4mEf[ 13+ ,Q?ê7ܳ|KkZ \VӖÚɈFCs-'Ru!$f _R<OhiĘM ڲ y 73*X$( 4 s/J=9.neJJS5%f뀧ۢA|*SQi5ޚS|OуC0+t먾0& UԮ$ %kE|ټDBk׭2x?ߜ(oeUDLT-->;YD'1]mM=s(؀6yX L.җ'oWK< h!s?|z]uv3)IZ W}=qpZS0vYS~Pw02^,$YK=r4=iwFdnbhd?qtBLզ%bi^¾rL+Mt豉Oh I c_qd:$y x5Au6~_:k~KWf8ݤ:(ÄEp*ZgB;ftQ2GgDr*Y')k Y]pU%j-`1Hm%KI.K-dy޽H8NG&oP^Q~rݤDbRT h!˔"N$aKH jWmؽm1 U\:W3Q ē럫)_ <̝:WnM])+d.!?^fr#$$h !;lJJB0 aD,XjWq(a3}lk\6z% e_8XgDᳯL@1#Mi߾7֠ԝS٫TttioY83qNMhNd!rXHZQLX\]VKQUSY%M1S9K[YbO4,FzS{؊<+!Cb.`m&$_dVgcEJ6m5N:>=5P IUөVa (d9=R6V@i^mèug5yXmeT= lt!c/P=SZnAʂ$a sqT鞙nfl ߘX]L=(LB A:(מ$zvMys"4`8DW~t ¨Zշ#J!T+LJmqMK'],3p# [U=g ?L({Qe{5(s\J!,&R ]NE۷͆4?W"(/5CK='_gDqm/~z5Eqm (63t݉\)F-meG>_mޗ&K&tn!L*?88͈Guީ uK_c!̥Eʿ50_,q7;q2]/ dw\8[`/J+6<\w1mpW/ld ,~Cx̔q ^Z@J0FL1f4ݰ$oDMÞ@P("<%$_gmp7"M?+t w)C6,FvDah| [=UEwƙRnpF>% I'mp/n^7r Y/.g Hz&4]:i#7ȯ:䘊6{a&%It@f:N"Kݔk$Q?:J&{F"I<| ^*(/fd]nۇ#WlU M|c]=7AJ>KjaO1&RK#zP]6[?P=V cF1q *1PJXc 9C%rTroyo[.)eu4.ȅ&H<7`iCVҸhV9l &g6⢭:ic i`I)c&ƢAq,PB=A )q\bXrޯ὿s]O\_5:Hgh SHe[LTKSǡVfmIyV' - etg9"Rïy%xr}D# Z%r?+z)Qa3z9{0bJo%h S#^3ӫ!ZM-Xsbn2B !6YÔaz8hh,/Q./JtWTz5Ǫu=^Դ&=w*Um(lAD3I\]P{ꕅܪ !ؐ1rˏ:)XT(q?uM)sWƍEB\-gpnQ~ p1) DCFͶV<=@NpG4>*0;&f%fڕ,8=Qa 4>W/Rh~/5gNp6$FIi.H751 gS;*eN&Hu9+]A=CzwN+F0uR"ʍ`0v|jNMc m,3x u>Q,"Ik~MJx2g0 PX?`Df縏kWꓶڷBtb6{=:a5!57!{oSȻ&-cJy'Jqd0(PPEU Tch&^r Nv}*ՅF( ߔzPs]/d~O&k.n/Xz+JY*@8p׻.(Cc~"#wD->" 0p S0HSʪ Z^]CYixTMSjE Ko˴6z?:E:*ڜ7 44d:n(*T\Qç[mcC#j{GMMu:E G<ږ_XA/-xz/onM7I`c dMi)MMeNW3uQz୴+D?w˱|N7o(RjT9DTqcWTkR?2ve+bEFIelt!K22`]n-hދ] rߺ=6 e *h/P<ޟ#0{r@ >x= ay?0X5e\>pWh?7}(B}cҴ"pGY+MGӿLe~Ցq$8NL֪1p$C#<$tc'H7Zo4) Yr)g"#{F,3}'\m=̔_$/O[Q^x 82J'F>Wܟd{ z04<|\/5ԃ+|=Ckj[xLIJ`+ ª-~In{TZHxD M;$x]2fU*{mJI'̤?J18!ktSp5͂a*e;)_[)y{Dfk4g.&Eg9_]u !oEnVeDA.EJ1&[zVC;}CmmєMҎ (5BkT(lRFT_zU4XoH,Qh!<T_bk6`θeBԛ#4&af+c]3aݑG}O|xN?lI}"zR |'jn fo_T"վhZ22]0'm)B4Q(C /Ő?a_O߯Q0S(+@2ɋȳNoj׳ *e+gHT#>3ɶ,Yo1|+r#ok@¹AɥJGF@iRx4F51[Pe.^G=)[+Kcuf{p!2~sZS>4>m UH'F.Yjr Mq~#pcyˮ989Zg^׼[nOHPW&kٺ|:k5¦(S$ S> S`"9Wd C#B #boã\_EAxOq> [%K ئL ?кn}CN$&Ⰷa:^|tICwjjY4FX{zDAi{Q"=>il فgEi#`?p 2-6U hNeF\\D y2 aydܗ#ѯwb,qw|qLNJrBnR R  #St4_x/; M{/"A;b>žęJM7mza5sћ }<ԩ3CA:YAnPžMKjIGm$wz )Vך Ω|&;;c3c Pޏ (0&7^ ݧvQRH%WFv-}ڳ:{{;*y;W5 $.<4P'Z7quM}/Eҹ2<(-h1-];/f 1h2 #t>LF0. HyzI!!8:[;B_ػ%"^aΝ[]?}9B8/2*;.fmIUu6G1Xi wc^[W-L (G_)]DQ@TEJG#|nTdnȤwt,@^a{V2a:{%++z;XwI9t=j2eUd Zb%H  {n>z0(T3dT! E.a#OȃE{nZ%DOh& 7z"w?ї?ۻ~v8 hm,Hd;G]}*,u B!ûmlZ<@ h㘦r;U[`d_O@S.O>nߣ{ Ej]Yk`Hh?(tk5jZ8"AJFBI$ OQjj\s\hY'_rߴCiWA&/^y'!,%GKA/nd_&Nv"&(q([NxK89ÇXgP25[Vlt8TJm5Л}tLM2Ӂǵv9Saf$'Ʊ?\|3N@qczV ŠBK] y&씲osͿ1#әw<0xZ/Z.?O~m_F.88m}l4 ^iPs\: F.=\7t܄D>mîlq7*=4>K]~u[+Ky~جW|9b[* Wc]R IcY`3*fQ2yĵ`RB w3VxUzRT/ dPĨ5[`Va.%pό /8M"˼sZ rdnrP~6[)+V-8,jUXw4ƀ̯':B@,қfln(#c3v8 rt,<;YFДTZ4JHUMsBKYa#jԥF>㶍Z,߫B{OWvE;^3m ?u"Lȕ=Ǟ8 M]m)їFz.I3[[3Sg:|I %ݪ?Hi+_ XLsZt?i1$]T|O#ة[Ky^T< 8E_*7|2OGqsw$ h=Ra.*u&Y皭lGҖF)pF2h=E 7x/lр$1_N-)BҮ;Q{Ba`xz.`)9@>Z9Em!?_u@ʀwroMupKև4,9^ jXi1+D.vek0#ۼwS@xe0jBU&&NY"赉- _BB*g]&k_6GsX><]m 0i0FtfB%a3|)mM"F_}oPfj8bZE~:[lL<)%۸`gt4ay$jC*O%[_R*|h0@;GE#2Yu!jao;.t;{:#0~vB Կ8YGh!摤Z}n$G0xt";@{϶,s]'* :jt/DK*,ʧDƝZaQ)<k+hPA0V5p?S SOwdUw. p#f:z=|^A[\DsnX1vUեx,vSQx&=Y7m#+VIzRC?7=>oQe pܢHo}xil Q0]vxOXm9+My ^z"aqf_ПUYqYrҒ>VH zGUTuӳ.O3eG\9x17Iwj+4t޸5`S AkJ|ߝ 3(U*/( p,|œD.u#A+2TRMdE瀉HXHufñS@PnlMi\#>M2ljp5Ȣr]_G7!2khh5EntZ"]XͼAMEw8rФ"U1cM*ֽ-\L~Ϧn! FP;(,Έ#itcW:ުƠ C;\>lbɁ ->dT]o_~ŗR?GY-h 0]̣?>KXCz%= is75zja2'O:7@@?9v`mO8?M ߩ!tW+Ri;{bẩ{@ I-ʣW?DL+F+ܖj0'-lr@\|!M <~)G5`k<MՏ.vnpkZ?-77QwPiS(?<)볒3sq~<,e4i1_S}V. OŌxM~֘+<ŞAHGaF7S+4ns~_^?Az'ԇcIֳwf%`] ! <7N+k}ѴU snחdedy"T{HHi,ua~evzM7P_tB΀AS5Tϯ+]utW`pcPc`ݞ_Lz]KT"}^DH0@->d>:q%V = tQS`8/Fco1xɢ{PY3Odd$zf~+Q 6}f#Wre>0I4`IT !\-n;Iqq#.Zv{sit؇>J2ƕ(R`>sR{?OjїXH{`0z6ܺV% b5g_#> F(܁C8-dcilꁏs#!QKUw05lG4J.lv{QQYu>TFq^!ro!v5._Ձ0*% ^pn)>ۦ9_*Ϫ "wXƗ&{zU ^GjN$$|f̰GAV-m;ar]uzBGaJ)`Y~[ ׎j_d#_$Xq')- ߙMODΣ:~mXΊ p'JYB=;Yrڗ 06C~U_K[B 0+`AAhh0%I*9Jb)l24 hu><QϜWƅCR~7o_mC1^ ++dJTjhKVwB{}^& YȺ52W B vQ95^XD[?Z1#+"F$/X!K9 Zip BZٕ3A Mqˍڑb4miC 9|ldۙgd.cVSq0 VFى;Bg߷AV3pM+C{(̇`qBH9%3#39LVΜﭖl%|oB͜nJBW̞6E㼙6C<~5m$BZ獛VPnB468u[S$k0_X֬0dʧ-V M9%+E5/!1D}pjfUzGRnuDzzʴI1ưp[`t+AY袚 @~-BʑBVTWL )‚r.%ǜ֪*?' fx7ׇ~6sۨj|%@zEM~s`!'4$'J~\_awm}v_m}/оCZ9s{  V/1vѾa[Ўh VӺK?&r ZCDm+VLŤE-K3]g-SUIOb(si.mM&&wi$_0mZ/8FP3+LzsFӺnͭ\6tVЉXu֊[Lag\u2ߝPsQ*4jxTB#3-ThS0M/1jA۷lE[ VDR:Ͼ2)MdP $<ݓ;|6Cdcw /˂3\')dB@PY*X-~}g J- .3?j R!nM$KipD8tx1 qJ&,m"S7xT@snkndt$8W_TOc,n"P B0MMvUapd -2\ (B:D2E/}e? O1rjygU85s(% W`bv G?^*>ݲ܁K/Rp8xēa`M}%eoi5Ns'Y9w3-jG-~ø3=UaCfwNru97e#|lFĂn<_ y P=) z PCז]_n.⥗遂_,5kV:h R)zTǀ,3xc~>Kf=L#!x 0ee*HU⏃ss=/iׅ͢s',K`$%->)2\˳NtsOHJ +!o%6Wr 0 O}_k\-RDk)v ǬfZщrqIL* +f p6VI'& &Dy)(w9b%C.rI{睌n RX+6Rh"m(ؗk:"p`a5^þ)::GDu7'e[q{󢣧[;%٩8c`Lͫb ֭ivU>X% b6c1Ӓr:0qD2z بW$m">c/؝/GJ|_'PrQm'.l1S9fVwV|͐'zXgwmt0?%iSW\ BB3l$V@vƾ:|i9犊%72n4}.P Yt_].;u$D2fpr`@ ~ uXdCU353oeƕUZ,ѵ :Ɖ^ ew3bGSM/aEQb[T)V'̵61UU^Ÿ|lYq8=Dq/S#`9L(&;m]tJ)]2U(+%q)3^>RTȚOfhcz?3%fC aW"Ugu<  CRm}61un;>ֈUK5L_TcE(!j.T&Nt(B|k;VRdHfZw?,dfW=[=TQ>%h:ç[l} h Q!c FAQMT2ӍX 7e?j,,볹 k- ѕu8%k]"'M{2ב޴snS0_R. L< ~ap d*FwO/Z9%`zRnax($xnBȹ~Esɿ_3n>ۇ: 66"/+hl,zB04b۶yr*PNe׭u1՝awc- u<'g('YAS}^dYު^.qPFѿ02&0Ϋ(}@r:Ydݫ"R2U?]&\PÍi)U:";"hty9D=rPTJ[]H5Yڄbs'7&IiΤLꨰ"prץ˹_oЮagyubikey-piv-manager-1.3.0/resources/yubikey-piv-manager.ico0000644000076500000240000007204212752100505023653 0ustar dagstaff00000000000000 hF  00 %V $9:(  AAFAAAAAFAA:AA<88AAsѪ@AAAiAAAA;099c̡ղ990:AAAA|AAAUǘUǘAAAAAAAAAf̣f͢AAAAAA$AAAAUǘUǘAAAAA"AAIAAAA;;AAAAAKAIAAAA@9qЩqЩ9@AAAAAHA$AAAAAA@<::<@AAAAAAA$AA|AAAAAAAAAAAAAAA}AAAAlAAAAAAAAAAAlAA2AYAdAdAYA3??(0` AAAAAAAAAAAAAALAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAA~A AAAAAAAAAAAAA?=;::;=?AAAAAAAAAAAAA0AAAAAAA@@JÒUǘvҬwҭUǘJÒ@@AAAAAAAA1AAPAAAAAA@:_ʞ_ʞ:@AAAAAAAOAAA/AAAAAA>AA>AAAAAAA1AAAAAAAAAAoШoϧAAAAAAAAAA AAAAA@<<@AAAAAA AAA~AAAAA<oϧVǙ@AAAPŕoϧ>AAAAAAAAAAA@A۾?AAADA@AAAAAAAAAA:JÑAAA?{ӯ:AAAAAAAAAA@`ʟf̢@AAAF_ʞ@AAAAAALAAAA@;AAA:@AAAAAKAAAAAJÒ9AAA@DŽJÒAAAAAAAAAAATǗٹ>AAAA:UǘAAAAAAAAAAA?vҬSƘAAAAAA]ʝwҭ?AAAAAAAAAA=EAAAAAAIÑ=AAAAAAAAAA;׵?AAAAAA@۽;AAAAAAAAAA:JÒAAAA?AA@Uǘ:AAAAAAAAAA:8AAABqЩ>AA7:AAAAAAAAAA;Uǘ?AA<|Ӱ9AA>bˡ;AAAAAAAAAA=:AAA8Wǚ@AA:=AAAAAAAAAA?uѬpШ?AA@\ɝٹ?AA?yҭvҬ?AAAAAAAAAAATǘLĒAAA@۽EAAALēUǘAAAAAAAAAAAJÒAAAAHOŔAAAAJÒAAAAAANAAAA@pШ@AAAWǙ|԰?AA@nϨ@AAAAALAAAAA@`ʟ?AAA=8AAA>_ʞ@AAAAAAAAAA:ڻ;AAA7PŖ?AA;׶:AAAAAA"AAAA@AnϨyӮ~Ա~Ա~ԱwҭyӮ~Ա~Ա~ԱzӮoШ>AAAAAAAAAAAAAAAAAAAAAAAA~AAAAA<AA>AAAAAAA0AAANAAAAAA@:`ʟ`ʟ:@AAAAAAAPAA0AAAAAAA@@JÒTǘuѬvҬTǗJÒ@@AAAAAAAA/AAAAAAAAAAAA?=;::;=?AAAAAAAAAAAAAA A~AAAAAAAAAAAAAAAAAAAAAAAAA~A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"AAAAAAAAAAAAAAAAAAAAAAANAAAAAAAAAAAAALAAAAAAAAAAAA??PNG  IHDR\rf IDATxyxW}?;FhmY9[6(qR $$^ʽ.^.s[\J -.@ dKĎc'ɶm$Θ,Y#iwު[0{48LO[{+@hǼ@%}(DLЀ p%g9S#6cZ,bRkwGu`+Аe' Gɱ{B@htb.3cMXZ ~ffB/`W%ND/8Gު!R`ykuke-Xp8ttA>H`v[{*j`Uk%Ђu~ oa'7I#p GL17wz Vu4pkgp8 H=8q]"SL[[j,Wuy(֮`/ S:b}铿u p3^XC愁.` 7sŶ֏;R:g^ Bp`80 ҷm+7g `x xJ0N=q`L|_5 -|d#0aumqq| a7WG20a_ V`}yl0 #@&u74ttͅiM1ۛރer.)9vpyF=]ֺ_9jvik78QQH}jVV_ϊ Ufk":KG.Npr`/^wq5=lWՄ mL~C$0EzAcR)SmZ^09Ɂ}sO B?fRM//&)$uUox*RU܌9ŇJw NgG8;*>wI&X V/nD.^Ʀwr+F\zޭT6G?v}AFOqS3vK"5ه,A,y20-ܽjѝT5;?'hT-e;mX6y G[ 9y+I6|r" Nyb8;EF;𺋰y*>)|'KϋĞ%qEmboj0"es=uF4_|hP*i?Rz1#BKI* : qqn0/6SĪPP֓*v|;~6t woܸMɔ;%R9׳*lS4m`G'qsÇy799@1PA(ՙpli:̭W|w<92q#b܂T .Uz-=6AIA-7+g)wc%67)p-[Aq@ு`ݩJ]@@Bq#B]I ۚw  &P`P 5|3lӥW#amݭl]r/66ABtoEZ}(}]1 iaI h3S_W~ʭlR*Om29 1`YƚA66V1*9(pS*6';BZ(龧ylqoMMVA6 -` @sy_Zov&CjXm@S9|uq.9I 7fur;qG)u&h*ߔHWw5r-%>H[q#ŠK p;iB>v$x.`VY0)5ps'x.M@C#aDy7 o|Sb.&%4Wl*6n?DPvwv0+H&P,/nu}Juz]EԖ1qS?NE8x'2æHP_NAژYT Qxn6 @ w/IEaWT 4W1@C#[w,ucERǝPP>\.`<2a%*57G0է:}#geu9̔lkJeekf󋳝o*W$wRS騈|>#"c34~ԏ!Tﭑ8Ԭ+j[j0#H{=/HP]?7.quŮWpv~[x0(5 J֠Ic(թ|~ǀ GHlfMeQʭ|]ssfe){NABT-W/eL(\kpiʚ 424e!Wr;r};tCDRM10v1.D XQu=63ƶݙ:pؙ` JHm]]+(p!:ȣx䷕tL$108K7\Ex3WOK͛U6V̀}Yd70lNfz,؊KLh]$$ou^> FvQQrb)͆MQ韪g!={yMX]fCϐ '34Mg'vKp GK~T\&=f'5;T2pL8[*8eTaSLH׭pR g,>&k/6tUh@r^gr VҞ-XE?0cԗHi5k.^~:=1c#č2UDsVR쭤EUV=c$"y`mXY}|RQȢRH%| :7?aXVUz躛 IuKKXKsS]P'Pm[љGJ?sťYZR_74ǩ9#d aRRPZfgƆUEieQw! h&sجPcQZL!k.v*c9B yL ~+y9+PYT.`m&?tYHgŤ`qJ}uJ. ~6-H"tfSvI~[lzXRQՍfR<#H+IybVռYYڛ0$o#:乇#]vHBP)g{} \MӹfIٮiko8nH.u dBO)[ܫJ&4hФ%Yꛯ{,+3\vi]w# \I M1rju "o[%s؜R쭤xd?FŒ9jVm,6@aiIc͋AH> &lݴnϺ9CX%0nZd#yK@Œq.Kpn0G{i݋\ƒ[l| p9NڇM(TPG1ߙJi%nH<h:h/{hN3s+gIAz.t Tq ުM,Er?JCJ_PCSRQrv!tϭ}tk䨴Œqs2L \4Uqo܃U(^>Vd) 4M'f;z|V/I"ԹjYQu}ǒԕpD^& e"tuy5e/]-u4h7wC<8^I٘ 3wKK2*LT#;p291g 4tJlX?,*]CJ `y7fH9tHJ@PRPCS9K.̓]{cOdĀpN̙ŲdS*>n`dL&@l M9R׿r"::KJie ey=T6F"f `rACZՎ]=wٟ $gcwuop:.ͫ0i) k ~4w s){Œr}J *CfuM j$ҽ΍L%xr`n0h(YU$enDP8ojp`. L[!b J},{knzG;y '%jVH*CAO$N@٫h~37X1ÑtᄂRl66NEDz#.݃[/§\LiJmgn.C0;E2k{޴|6e (KU[-s]gfƲT.XvE\%_a %Tɒ+)I* ǀlIf?@;2VlFխ{y fƳJl20)-tur Gl–{wr5>JIOVT_}>vPYں[ =B^}c}/&z>\p?v˄a JְRj48`#+77@#>! W"N&)9d#6*ds$՟&UEKiySr Dg BK#vqlü|8`3\k~@E IEQ#oSm$8`34Ml!X^y %ժq[Ĭ]u P.lҒ C+ ' [ شNKFUQ3L=FG.\W)tqU^#L"6/yjClU d`C+=G~o %{8L$'`潤iS^y3E4Wnp{):^ߜL'rnNAoZq  5;2je74Mc]ݭͰ%h/㝲[Ή-' 't !8!w1Wڢ~\hk0vIDATh,J_n|a0Iw Lln ' MVQSܪgƆ;Ta[\6P (w􏝒=d^P^X]% Sa2ANNfYr1]2tEsVÔ%8{*rv +|]!|{.V \6&DaC eJE~@܌r6ö"|޶HPk =l^P)r+~o0x>s7a;%_M~I2(lpӄ `EuͰ5X]#'#vNT3s$dS_ʭ=at]ThhăJ\`1^<]C+|-s}mseG^zn@Nq1K(-hbjlP||D7+97"4fJSy+m% 3ʛsms~IM%P #=+j-ئX77la8EhIS=xnec!+/6RÌz4;1cAu '{h3}kbP u} Mˮ&1qc͍8 XlM~\r֜D#P4b8aյ7SW IyR4whO;.!ҿ܃ @Ĥ)ш>,w<å/Yb;y1c}BwR;NŒrfe"ĺq'4G)$W2DSymMWl x4WluNCŒ9[g/tQlK5rDyGIA ˕]f57sJ~MӃdg ,(tp(MiDyi%K+*DEu41(K;0&J1$cQNqLC$N`"+.j:n>bqڏkRsIЉL)itt>N9l^p%C0W.zU/]C ~ R䚦3g Qb Kʯ:n>"W;C `E87a>ȼٟ1푾"N%RG4Mƭ䩶VrF 02å9;|qN©}t_¤eUK$: #f[_Vҝg$FT m[Ώa,>$=EtV TLL< Kpzp=m 8޷H|X!,ipz%Uο2- {5Mg$CG."qgpne18MXV΄N|Mߎ7-3]@l oC% (I)+lܷX&CLM02  MlGrT Kpr`/'v-KV_Pm#Sd$i΄]H>xżĂ|ïKw, WO]*# 3¾34yN@1S$ ĥN,93_A {YZ*'wZBǾ:8'la<J/s`ptRoURGCzRE''?@Ҏe ]ssvUN[p;9 DXRT5S]\y|7 BLWE4a.a9;tH;Ɂ=Jn?MWR˞?htϪg{2]a `5ޣJ\`_O)ȂSAbƸ_BOWd,DLaoMUVŸG4e `#=;T |DI>U57Rqj]׺oCEHd8$wP L5W|}hbO1aR)ڥ:n7vg%V /$;yd~"o^0'*i&njJZTjǒ>)es "=u08=xxq=r=y+0f, Mij64FN|[ 9pPI@t6H;_3)i"N|S Y#pbqH|c}/29z\dNi2WN塗LsF+Z]4)c<?}AUuS? BeҀ'\sACseNQiƜ<]E?T;'˼tɧR^~ $ \s%nDxshچ(6EѿcJJLapSK79c~h?V\WȂL]dL24M vqe =y=Bޢdl;qcT;* }, @Y|Y!z+M1yJ:`]ݭǵ;·u.l%HJ!@pj%^ZSfȓGEaw+μ4E2!Jy!{wHo2p.;[tE8C药rSM7SRK*B4p̡_x|PeO!s@(xH<]$ӥ{zuGU1:$ O`6y&+9M@=~3!kdϠ0C{?'q̏1 3*+eydܿҷ6yMN~T%3pQiw.,o`i!hʭR_Js&*&aYUs#knJ?;yN Qv3?0fɳKZ|KDb3o[ U2)1sL\q&~,x|G~3C/aOr02WOw7@yG,)@\I6b<>CK>ub\ L& }83ACC4Nу#\ïmߗ9C0# [BCt:^'9q#td2m7 )LaP6}  WtOB\!G;tQXt*tŹWc]]RFc/qb`'ۙA߹ mZ 37}e[U C][O:;ИxhǪ#Tg2Y~Ì\߻"HiA^Yk=ƮP)U_KH}%☿bS=t;{8WyL,1̏)Vq Q"R'?tg>ʶa:tMŒp'SS"3$b2T_aR{Nٓ#6++ڥtj2X {1YȎ|)NyOG7p/MDcW\N ~F9\ƍ/u=gb(rNu$PJ |/(G4Wl ؊=rd.W_,`ZUV fS_ŊY_6jA7v>Ƒv#U˔B*'?(pN$DnಝXCŶ"=y|[T>08$ϟG{!jCN@_M&նL~ojX* 6jA9 &}D<Pl򪥿mWaT*5*C!LJ}u,mMSfòc>K4Fa{nBeM.T)5j.̓loz%5͚$c_pדfÌ έ@ՆL6XՅm#p94MgM7O7/63C_cߙ4 3@h ۽D`V'ZFSԕpK˧h(]ڜ;M:^[ B~l()bnヵ nDhƆibɈ g]7_B\a_ q⧰+&waNu*!Lfʢ,)5KO^+Y+:g‰o0k>cxS0m6$dkq3BkKSz(H!ՍHi? Eҽuyq̄Q>>!O^4`j) f Ìuɽ躛͋{(p8DӜzΡW8=x^Wa^m0fGoTE">m6B$X\54d2\'٦ھ0.u G{ړyntݝQ`Odջ&ޥڦ\a a(/\DSU%,*]GOo^w0(!$ Äc}hh tpH>G@hfAŷiC_גv"uf_ϳT,(KvUwKXA=?5S,8HY my!>3gʂd7xȂJ,Z@ vX?ł8B0_?JvR#I&6p/+!^~ < Yq`v.X)~9d Ši0gO#S0Aہwؕaq1@h9 BPչ.~v9\5x:33 vJ@B2ǰҾPb{Gf!k>ܭдO{WҽΪ?3%:5[ƁcΤ=d Bf ܀O0ڀ s iğ;dM ہ8Y@;` !Imt8VZ5J O%*b:?78c& +h!s,'7Љ%Τ=(bw[{ X5花2cXG-/%L~kobexx6=DW#6a Aаڞ]Gk{\Z_*~c #sp0_r&}p\n Xb\^;^ a~)S B'ɺ1L֍6U9@hg9|$sppppppppppppK? IENDB`yubikey-piv-manager-1.3.0/resources/yubikey-piv-manager.png0000644000076500000240000001400112752100505023654 0ustar dagstaff00000000000000PNG  IHDR>abKGD pHYs.#.#x?vtIME  !1}IDATxgp\uv ;EXDU{!*ȑ#YXJ,kb%;KH'3eDZc'A S"(AX,X.{`@$-(}{ι1[4Ӿդ<5ۛcGN@#P"]; AI\BF@,pk2=2!dRD=no|0e G} a[$zBvY-O?n+"L|_&4y,x3P l2RVVx8 yEM`<XuYG4y6WulxhEA|CjXw{4[jG,,vyގE&p'v6^5osahr eu_"2 ^𸽇-[^xټ"@sk 2Cg56i2LdAW#y KNLnr=F ΂XDd*q8Dp<D *Ba-+K/k?Õ%N؀ Eٶ37H, KeyMj,O5c%f)cGQ҆\{h4 O !MBu&*W 2F9E IqN'axSGv&R"mYiYK;{=%Ha18Csjxgt FOn ZM|[i[&{ s6#+֣,±G^WD^ɈPr Ի6^R-(U` 0*WRf]yRڈC^a`YtZ=WV~%E*46եEik>kTW 㡨cûU!ޗ./BsP[M`;`HZiib ¸>Jnij~SCQzR*+Ӷfjэ5w+}76Dy9 6ܩxKlږ NR> hHAZ#Kn@d*E*5' `G#jpDDĤO2pd`ઝWb+,5Js%@ 2ږRڨZ ߒ-Zƒzڅ0tĶDmhs=uΫmȚ[6\r[<庆\6P\/|P2>J,#+LU2G.K5ʶ*8f(JVd̆5R`*j;ۿ_lR]QVL->u)18c,U»I"i a|rHUTW>4]h%inmnVȵwoYѳԝ5bE IMBֿyU[k 0 GȫF!sk#fLaUY&X0hv WPXq(SV |6>y! / !5VmE0'NoO ML0PS UN˛[@Z`mE.[ʶbYvnN?7{JlK)\GQj[BUd>ѵdV:VIh&BI$/L%8{GHږf?()`)t\QMx v3vsdE(kf,hsVƠT,OaCVεI4:Eo{/DDh/)l`\\?:Y)bPΜ}FV,L7(]@z!h:JjU=6˰/J66-NaܾN6Vap(û&I]u%HiwRT[-3<>>x](@1c`۰JvkkF%H%` >hyӣ ; LWnr8@t0W|gqf\#x}+wm 2p1-RZy F-gi:j+ N;Ӡ5P9gˬFcL\iJ#&wC8{pnF=Bomm9{ΌNc@(f,Bz}Ŷ*b#Sg6DԟV{ Z+Ա B$Ŝ6+ܶ7X˸ ֔cb"L%u璢-K2\LD}iuYsm黧H)-9tZp\0:̑# Z3nf>@;^bM*iXNP.K-+3`Վ+jtyqVS#}jK-u4iW+PȀP$4ΐWWSl OU!j$>VC0,DfgF[L'M'@'!Qj@-sē qZ{HBZesQڼ"bW%`TCי2h4lv)b~&BUPR@`: `җ7vV޵)#!jΫ)0 reizd-:mU# ]HyVt4΋ :kRw@dP'3lH{Dz!`7)͢=WVF!09DtDXxޣ R$?J"˛1,,)ڢּ#`% Yi~ޱv&H F(-H:X~8/4f#~O˗¬nP[u Ot ,s >1F&NJ%;(6Pba#n`l.j"2>9 w]P/W;\)F( ʤ^|k]Y;Jpu\594+#]۶7F&}`Gs:~A ?=oR&(m2e{GHC-o _EH B۶7Tf usfSsZv^ >3K?~Tͮ_eGWx.{xP/ǻipYkr^Tmbc*;~W6yJ"@skljI"FB.2']a?b,'F[D?zkfyɍߐS=`+q{CI'Wˎ۶7Z-HW^Nrz}h˦8hĿplx%ZQ/"HW_31q6.9<q:d_ׄbL<&|ŭ˗O} EAytzM:mM5~ ӷGI\ԅBrFi$2j9,y%8-5Y XN;֦4]kjj _u$ چPlmi@Yr2ug DL09tj 2@('%0)ddc϶{ \N2 n׬^a;04: &y"ؘ\1ڀ^DŲt pacY+lB:eC >,/yx0 O6QR'JrLP 7DN6d"E&H:6"C'Q`=p5Q+ 1x,k6.BWᐬwO/|9b^z[$c1Py1* ?tRWfY(w[IENDB`yubikey-piv-manager-1.3.0/screenshot.png0000644000076500000240000004000112752100505020131 0ustar dagstaff00000000000000PNG  IHDR1@esRGBgAMA a pHYsodtEXtSoftwarepaint.net 4.0.5e2e?rIDATx^ݓyHJ •Jrĕ:\uV%7E[ B;vlI;![q6HAևA`,$͌$bH]W왽w~]zݽ~3HK|uB!ԭd!B OB!Խd!B OB!Խd!B OB!Խd!B OB!Խd!B OB!Խd!Bݫ'% &BW[O0 cp!4 3; [t YO0WYܢcdBx2΂'Ch“gv<B `8 Edsř-:O,'h櫓T΂'Ch';cON> n¹v189=.Zo<B=ٹշ|b#?/U 87gv63_~2[t YhʞGq] {g˟䗔}PUJPin"sd g s2[t YhL9i^x[W_X__ոz}E3SivàOoOO} OO؀N<B<ٹշq.}nL+N!t1f)疌ji&dfSU3f;LO7g,&k.yӇ(?|6Mz ;Cp!4 M͓Ğ]7dʐݸ/W=Wb)ux|G7n~lJSϜtZu&{Qͳ+f+` \ݩ~ѥvd<Bt<ٕ_=h |k7EG7~ws^[䍯^yγo0 nopnLv#o0VM!]OW 5?/Nxz>kyB yjR}BLs{ɜɎ_#r})\;oWyó{~pI{C?r0nWlwf]nیvĐ wh@< 1ZK7gң\PuyLֶC^9u5_$E.לs(_V=2s s!Ms>'ГשƖE~rm˟_Fv9cס۾_ܼUi95O>J[tT.kF[I$c̡{˓9;= c9r]XZ-sb]AfYĚSu2?MLBѫ;2?fXML7?E^FTo̾Kӳen1x2fx_ke>%20v]o{_wϸJC^J԰[F'os[p=r{4JvS}xR$E.:sd J8L8KE#LrT~ ' D\VΩ}KMGfV2S'U}p&ߓ<B = wG\O6C.hh?F[IH7(fs5d$ bftXOnuVN"5Ԣ2ԏiBR@>ϴD W?e@ZkQZLrZqc*pfgAp!4 MgNJ]*nxӅ w_5?*f7(%JƚrFg؄%2Au'蔰""1g&G47MHGdfOMΞ.S?΂'Ch'K. kn߽OW7oؠB%wa̜4[fvF%3y孶~Ճg"rd *.HDhrrr2^]Pt-3 u ,n1x2fy2Eo_"ݘ7] YR-ѣ(fugR9mnp%$")=92u1?͕2e| YhV f <\( x2f!<L YO'Ch“dd0x2f!<L YO'Ch“d6A^1x2fz7BA O<'C! y OB(/<B !P^x2)<BdSx2Byd!“!4OГ?H~?o˱5V=ϞI-'zBhA'Ch'k7x2dSxHx2 “!4OudD5談>q_;Am H{2 E/7.Tvqeu;9nߏ6?V6ۿvOVi6ɮL+Wzwda4OĨM_c+x2X 'x;#' yʭ Fmjy?ēA_lmm“I|ir+'yʭ O  B)<,dW~rjٟ3w)ƩgUuhH|R2^/<,d\[=>phBPuR`$>Q| '4dJn1x2X06_9ͣw&졟}/_RϫCT]*A0tF΂Z2%<,di b | ARWT:3vnxݷr?qGx.`I|RK#[A I=/}v}nL+N!&`1HZ4dmIoK˓@5}Ov+;Z7=W~߭lJߨW] NS<!ImLy2_tiuuUWڳ_#r}rV=?curz}塟J 9+!.rcg\T;TX!wݶ.MRB ,)#Fe'%l'5HK`ȓ?/~ٍ؎]n?֣n+qǶW}Ԑ{W3iv!ΧdԝwDr2 "r\妮] g[,`$>^vP˭ _Lm\|YT+++oF'{.e>%^=g/;xۉg\A%!ϜϕؐTP69QH贒qBljIRKktY%I[A {]T]o? B+s-2۱Q}PZ%NkXKa4O`ȓ]+~v~QL?ܽ~uȟ].sr~%~\FSi(yaɺpG)yʭ _Lm\t޻p¯~FOvj+vhv?GkB;cXZx{2粊89)ܸ~ Y4O˓K;.i|EuG]xOnd߅a׍w T. -vZDk/ O bd}Bȇe-f|p<-[__q}[͙j(Cfm%̿=˜RQoNlBbs'댒 mf`HkF*ۜdrd0u4O˓#έuGƬ;/>5oj c=8t{yΝ;ғ)A'W7ȡwxo#:@bd񄊶3M+9\h֢SUdjMTIL6K)~y2ݎ#*Kvw$}nӆHW A]P! b:/^\]]}wO:ғ)οG:M;;Ǿ/):TAͣ1d!I[A 7~(&Y F^*z?˝9d_:c3 R*x譯#KOS}1=٬?5O#w׿Ƚ{Ƣy2YWB4vT{Ϙ iT` g91)k(b!ZN+7_:bR0hcrbIUE9LC՗HtT,#OfN*wc<͔2Qr_-mc`hGУ >:ӽȽ{y2,ح?d#v$4vi P1XP5ܘ3e*?*Q(M­;\:00JWm~7>OvUfQuva%z_ϭ.I_v'MN=z^jl&uKDOST%DdZuZ\!Fj\h~>m6M~ /ܪ#mY|*F]RS\Fw}XDOfb;pInRf/2kC ٲBT0lǁ3)+ëd+dNF6ux ?gj jA](Zm:y`J},˙R\=^cYf&٢+>&"7]`&JlԓmnjgܧF]M߁իU\d`379u3rmrH27%$1C04BF˳*ʸdЖAJrë lYLDio7]ek"C; {B7mK_/FuWma -P=͘QwVZ>~T~`WvQzpVI[8bҝnW66Ѯ]Y LpnːAk/0!uA|\ E51qG(+k0ըk?":+nЏG|‘m'kG(k}钹-d%ݢ dCUـMȧR-uܑOj{TXUUQ 9$]w]5J"٠hG'.G`@0u8eNez{ålƏܫ60Fdtwn\nq ?imJ.[& }n-ȬAؘ<)y((NJEf~Q!XZ|_JurpjU? 29 Y\,r}U{4ճES\ތܻg ;nH;K,zukF[zA^E0ӰlO6!`v GHɺLͤv1.l5&db*pAځrL hO0J2֓-)[Zv)2[&v6(trxTozΒgZ3u3r1Of ,Ҕ9d#1*h;,r٪"-SVoҚLGQvx'>AlMc'bPMy, K[,¡\dhg\\P_xxTvlirŶ[/Vw`نGܰpMȣ7;gHeK`gʶ hG0W"*HQ}Td0͔2Xў` e;%pwWS$.]&v6(ϘvNkbw}XPO6O[\`ӂ=/E?: Lqj/򺅶 _F}Ԍ􈚱EI{\k-qhiykxL1Rrs3 hG0W~Uk0D :Q% bHCfs0{L~ܳ%7MΊ% ʓ 29AVnvZCTvlir-P{ ׮׼uLAhӶ4*7R0_fG;z27;ʯAG#uEϴET.;SԎ>vL~ 9k|MWN%,]2=/nN2wctqw}@lQ-<=:t#W:G0S@}@]3d0d}F]o-m=c:tchG0 ◷,K1_#i> x2x>]KKw}fN'n z%:tt#> dU0Kٳ:+uSWӵ+~ēy;G}@]3ʓmllߤchBwnU51IoʑGL]&Ϩyv\w}X[8m6o1#|4c+&ϼp'3fnSiǢy2[P82ݲ]\wavL-Jq,jLkس?;I~E0rOFCe|R9 SL[35x2dv3ޱ[mv6v.%w"Wm#tPvU)i4,F l\_cs (2 "E-zTG },(fD₵x2cdfC^%Aѓv*AW>@ g b=(.%_3+C;,ר@ilv?:IӆNy۞lY\& z(r$B;{?Y孥ct%vMҙ/2`q%)F8H'soJG/_r`Q0jAՌI{<^lfTD;n}Cџd|+#*((GR6;C:'YigO?Tqx2)}٥{"Ǣ3u0娀Fȵ3ZEAkdtǢqdɡ-r,:S}-Gdx \4;{|[SyB5:O#__a0Lrbo rAo痜ˌ$s?:Ƞb'"2ٷɊY„wdէ͌$=x2){;zrDl,^2aTH2lKY/KhW]r *Jt,:x"|Pڃ903k.U_ΔAE)UHƓWgqPcu؎avݭ7U={\l *2qs2=z.՜~6S8C`t=mKzL`,`  O#Oe$?#x2)x2x2)x2x2)x2x2)x2x2)x2x2)x2x2)x2x2)x2x2)x2x2)x2x2)dv,ݺ;(Qѫ>MHdCOB'҉'ӎ)o%an6 xr#u3e! Yܧbtʊbɀ F}zdlm\h6FTx*YCQ<n'ӏ|7^2vQ^"}soe!o\\8?J7]XkzL-!̐lN?)l- R2dI* r(taQנ TO#]Ə{m Ͽ5o}Lɢ_Vun:fTNhm}èJLP^v˨z!2T'V"$~T3O`)\23("-{Z8{ )ȫ.<-Ϩ <~L>'<4 y-2)MU4MmڲrmtB<*!h5P^7{ڎ,%+M[<)aUE'IU,)JJ嫦ڝQ}d$)BC."wrQP#vɜ=t+s% dEIs"YFxiKyREn鸸՟Ty%U`t;œ+o 2=y2'h=ʏvKSVPyjftYxlrlbl k%W!I7qFP'Гylw _z28X(H$DƁ=cPZeB #IGtvPX , 9]LL8O#3O]g6y|mYU ё~ǘBUnhx֜\ b&۔Rg^-lΓ֮KOϵ5kh3jrPYp蕈`/dhN^.5`2/xlLrO4JKvTqD,2d0KMB(RFw5NwtOI)M'DwbcgP`8*BDU3JnXR_i<#*q`yF/j5 Hݕ8GQtr@t)5D[/OCO[wP/ˆib@Q*_ڪޏ!sÁD4iĉVŢDA,//Ke$Ŕ%JɁ7؂Dv:] cKG'QJ2j}T|$U 2^qQ>2Te?]4wtPLY4# N <.=Y!5';pD&/!T'z̈+W@-ݾ_YVs(/)Ce isʁŢEq=rӉT*IyťNr9'"1"348%Rʒ+/J1!@R*P iRd0R:d0kw.&x2)x2lH0rx F i''^''^''^''^''^''^''^''^'ҕ'S9\p7}*m*@Г1CY0 OCOv`?< 0& ܦ< <.x`$LAMx2zAs#a m*@̚gFDTځ''56OCOv`?< 0& ܦ< =%'/ܺٺ%Q\,ؿ7 O&hpJ;d0dSEm$~qOwB[9'LwyáPF^Ý&mM16v` ɦʬ_ӭ)u?}ʈ|T{KsӲSlf9LAMx2z2wC = Gb_[bɍc&ss[` ti,">:Z@X.']dl4bBy;ٻ#[E&'ӊQ1#O,hpJ;d0:d }5}w9rVUKnؖi!W6MfQR+~{0l` f0RꟋ> eP*ѽWݫ&Htԇy:d m*XOP=޾|'4^{oM&Q"97#zчj kTo46R\ Y(#o-]QXcr550sr} 0|& ܦҎ{2\rkkkgΜmʎ+9^I*u$ZhϤ X@2[D['2靦XcM< \PΩC>/7VGk(mnSi Bٷ_@[奥}X ?`Z09~!ڹ`C(a?}ʈC{mN mnSi BLM:8./m;g^PB̴\4 ʂEGwgYБ0҆j.ڥ.ƆuR\ Y(#n-˃ҭ{;M"._?ENdNhL~d m*@Mڕl}ʴfv82EB*DM)"FM%s/tiW @2~H?RqXӝ 3jK$mǪOhpJ;d0d] 0|Ɤq? 0"& ܦ< =x]K[;S\Y16v` ɺO6.Ɤq? 0"& ܦ< = 8.x`$LAMx2x2YS\,HhpJ;d0:dAs#a m*@̚gFDTѱ'oӧ?~9{2.` 6vt{キv9`W`W`W`W`W`Wԓ̚Ǐkǎ{d9}dEUݲœ@hՍ'S9ZO<0t=4x2ՙ'0=4x2'-x2 Fu <B“<OPdO!ԨW|}L\1iUeV)a1\7Hqo s۔6.&'3/"i9ut>fk뎴n7qu#OP:d }u{P}:v^qm Y14/(kJz# ywDdo+Zl('ddHN-F[A[XGEA-2G FfDxo͛LEr8'T㱎lo,93x#w/5gZT$`DN!Q5;r5erx@oZt!+ <B“͈7ۖN qt8lXG67ԟ1aG3d^1Dvh7g*t6END(?tzt7ax2ՙ'8p-7lT(e*À9RtPud|CYG'sy@|y$'t|,Txyq$Vu%pU* { F Fu-^r(+R[f%GRtg9BCѓAnq{13N)-7eHOPZgoْm}L2RbuN큀'CQx`_o;m]wr5>%ysWhh([;VMHid{Ȍʓ)[LQA#{/ٶ')X^]>horIKN޵E3¶szU{ jsW9 y2hBa٭VjTgl߻ki˗_t0<=˃◷,=w0"d5 O9x'zGid53O0=4x2'-x2 Fu.FO Ff @'CQy2jBh$jzdܦ<dYdh“@ O=OF+<<d#dh“@ O=OF+<<d#dh“@ O=OF+<<d#dh“@jʞ `Mx24Mӓ<d`'O#<,0x24`O Fx2X`dh0“'C <d`'O#<,0x24`O F<@oqU -='C '< <Z8`;6œ@О쫇'{OCƓ='{OAmjjkS -^ïݱm'65ݱBx^kGd0Ԧ6}p'C}L{2'nOU*!B7Ԏ5mPϤlÿWʶ_޶%BhjGuL O&vuZsY^E!zx2zd n-BGV)RB! !B{B! !B{B! !B{B! !B{B!# IENDB`yubikey-piv-manager-1.3.0/setup.cfg0000644000076500000240000000021512755276415017113 0ustar dagstaff00000000000000[flake8] max-line-length = 80 exclude = .*/, vendor/, pivman/qt_resources.py [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 yubikey-piv-manager-1.3.0/setup.py0000755000076500000240000000464212754543003017004 0ustar dagstaff00000000000000# Copyright (c) 2014 Yubico AB # All rights reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or # combining it with the OpenSSL project's OpenSSL library (or a # modified version of that library), containing parts covered by the # terms of the OpenSSL or SSLeay licenses, We grant you additional # permission to convey the resulting work. Corresponding Source for a # non-source form of such a combination shall include the source code # for the parts of OpenSSL used as well as that of the covered work. from pivman.yubicommon.setup.exe import executable from pivman.yubicommon.setup.qt import qt_resources from pivman.yubicommon.setup import setup setup( name='yubikey-piv-manager', long_name='YubiKey PIV Manager', author='Dain Nilsson', author_email='dain@yubico.com', maintainer='Yubico Open Source Maintainers', maintainer_email='ossmaint@yubico.com', url='https://github.com/Yubico/yubikey-piv-manager', description='Tool for configuring your PIV-enabled YubiKey.', license='GPLv3+', entry_points={ 'gui_scripts': ['pivman=pivman.__main__:main'] }, install_requires=['PySide'], yc_requires=['ctypes', 'qt'], test_suite='test', tests_require=[''], cmdclass={'executable': executable, 'qt_resources': qt_resources('pivman')}, classifiers=[ 'License :: OSI Approved :: ' + 'GNU General Public License v3 or later (GPLv3+)', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Development Status :: 5 - Production/Stable', 'Environment :: X11 Applications :: Qt', 'Intended Audience :: End Users/Desktop', 'Topic :: Security :: Cryptography', 'Topic :: Utilities' ] ) yubikey-piv-manager-1.3.0/yubikey_piv_manager.egg-info/0000755000076500000240000000000012755276415023017 5ustar dagstaff00000000000000yubikey-piv-manager-1.3.0/yubikey_piv_manager.egg-info/dependency_links.txt0000644000076500000240000000000112755276415027065 0ustar dagstaff00000000000000 yubikey-piv-manager-1.3.0/yubikey_piv_manager.egg-info/entry_points.txt0000644000076500000240000000005512755276415026315 0ustar dagstaff00000000000000[gui_scripts] pivman = pivman.__main__:main yubikey-piv-manager-1.3.0/yubikey_piv_manager.egg-info/PKG-INFO0000644000076500000240000000131312755276415024112 0ustar dagstaff00000000000000Metadata-Version: 1.1 Name: yubikey-piv-manager Version: 1.3.0 Summary: Tool for configuring your PIV-enabled YubiKey. Home-page: https://github.com/Yubico/yubikey-piv-manager Author: Yubico Open Source Maintainers Author-email: ossmaint@yubico.com License: GPLv3+ Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: X11 Applications :: Qt Classifier: Intended Audience :: End Users/Desktop Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Utilities yubikey-piv-manager-1.3.0/yubikey_piv_manager.egg-info/requires.txt0000644000076500000240000000000712755276415025414 0ustar dagstaff00000000000000PySide yubikey-piv-manager-1.3.0/yubikey_piv_manager.egg-info/SOURCES.txt0000644000076500000240000000324012755276415024702 0ustar dagstaff00000000000000COPYING ChangeLog MANIFEST.in NEWS README screenshot.png setup.cfg setup.py doc/Certificates.adoc doc/Device_Setup.adoc doc/PIN_and_Management_Key.adoc doc/Settings_and_Group_Policy.adoc doc/development.adoc man/pivman.1 pivman/__init__.py pivman/__main__.py pivman/controller.py pivman/libykpiv.py pivman/messages.py pivman/piv.py pivman/piv_cmd.py pivman/qt_resources.py pivman/storage.py pivman/utils.py pivman/watcher.py pivman/view/__init__.py pivman/view/cert.py pivman/view/generate_dialog.py pivman/view/init_dialog.py pivman/view/main.py pivman/view/manage.py pivman/view/set_key_dialog.py pivman/view/set_pin_dialog.py pivman/view/settings_dialog.py pivman/view/usage_policy_dialog.py pivman/view/utils.py pivman/yubicommon/__init__.py pivman/yubicommon/compat.py pivman/yubicommon/ctypes/__init__.py pivman/yubicommon/ctypes/libloader.py pivman/yubicommon/qt/__init__.py pivman/yubicommon/qt/classes.py pivman/yubicommon/qt/osx.py pivman/yubicommon/qt/settings.py pivman/yubicommon/qt/utils.py pivman/yubicommon/qt/worker.py pivman/yubicommon/setup/__init__.py pivman/yubicommon/setup/exe.py pivman/yubicommon/setup/pyinstaller_spec.py pivman/yubicommon/setup/qt.py qt_resources/pivman.png resources/installer_bg.png resources/osx-installer.pkgproj resources/pivman.desktop resources/pivman.xpm resources/win-installer.nsi resources/yubikey-piv-manager.icns resources/yubikey-piv-manager.ico resources/yubikey-piv-manager.png yubikey_piv_manager.egg-info/PKG-INFO yubikey_piv_manager.egg-info/SOURCES.txt yubikey_piv_manager.egg-info/dependency_links.txt yubikey_piv_manager.egg-info/entry_points.txt yubikey_piv_manager.egg-info/requires.txt yubikey_piv_manager.egg-info/top_level.txtyubikey-piv-manager-1.3.0/yubikey_piv_manager.egg-info/top_level.txt0000644000076500000240000000000712755276415025546 0ustar dagstaff00000000000000pivman