Compare commits

..

No commits in common. "master" and "python" have entirely different histories.

22 changed files with 667 additions and 1164 deletions

View File

@ -1,31 +0,0 @@
---
kind: pipeline
name: test
steps:
- name: check
image: iamthefij/drone-pre-commit:personal
---
kind: pipeline
name: notify
depends_on:
- test
trigger:
status:
- failure
steps:
- name: notify
image: drillster/drone-email
settings:
host:
from_secret: SMTP_HOST # pragma: whitelist secret
username:
from_secret: SMTP_USER # pragma: whitelist secret
password:
from_secret: SMTP_PASS # pragma: whitelist secret
from: drone@iamthefij.com

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "ykoath"]
path = ykoath
url = https://github.com/vividboarder/ykoath

View File

@ -1,13 +1,26 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 1553c96e2a0d0154f3aca4c5cb0156a74a8c703d
hooks:
- id: check-added-large-files
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- repo: https://github.com/golangci/golangci-lint
rev: v1.52.2
- id: trailing-whitespace
- id: end-of-file-fixer
- id: autopep8-wrapper
exclude: ./src/vendor/.+
args:
- -i
- --ignore=E265,E309,E501
- id: debug-statements
exclude: ./src/vendor/.+
language_version: python2.7
- id: flake8
exclude: ./src/vendor/.+
language_version: python2.7
- id: check-yaml
- id: check-json
- id: name-tests-test
exclude: tests/(common.py|util.py|(helpers)/(.+).py)
- repo: git://github.com/asottile/reorder_python_imports
sha: ab609b9b982729dfc287b4e75963c0c4de254a31
hooks:
- id: golangci-lint
- id: reorder-python-imports
exclude: ./src/vendor/.+
language_version: python2.7

View File

@ -1,19 +0,0 @@
Copyright (c) 2020 Ian Fijolek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

48
Makefile Normal file
View File

@ -0,0 +1,48 @@
.PHONY: default
default: run
.PHONY: build
build: venv install-ports
.PHONY: install
install: venv
./replace-workflow.sh
Yauth.alfredWorkflow: venv
mkdir Yauth.alfredWorkflow
cp -r alfred_yauth Yauth.alfredworkflow/
cp -r venv Yauth.alfredWorkflow/
cp info.plist Yauth.alfredWorkflow/
cp icon.png Yauth.alfredWorkflow/
# Installs required MacPorts
.PHONY: install-ports
install-ports:
sudo port install swig swig-python ykpers libu2f-host libusb
# Creates venv using MacPorts Python (Required for it to refrence libusb)
venv:
virtualenv --python=/opt/local/bin/python2.7 venv
./venv/bin/pip install -r ./requirements.txt
# Simple execution of the workflow to see all results
.PHONY: run
run: venv
@./venv/bin/python -m alfred_yauth.main
# Runs workflow and prompts for Yubikey password
.PHONY: set-password
set-password: venv
@./venv/bin/python -m alfred_yauth.main set-password
# Clears the virtualenv and other installed files
.PHONY: clean
clean:
rm -fr venv Yauth.alfredWorkflow
find . -name '*.pyc' -delete
find . -name '__pycache__' -delete
# Install precommit hooks
.PHONY: intall-hooks
install-hooks:
tox -e pre-commit -- install -f --install-hooks

View File

@ -1,23 +1,37 @@
# Alfred Yubico Auth
This workflow allows quick searching and filling, and copying of OTP codes from a supported Yubikey.
An Alfred Workflow for auto filling authentication codes stored on your Yubikey.
So far, it has been tested with a Yubikey NEO on a MacBook Pro running macOS Catalina. I have no other devices to test with, but bug reports and patches may still be reviewed.
## Notes
## Cloning
This is definitely a work in progress. There are a lot of rough edges yet to be polished, but here it goes.
Currently this package depends on a fork of [yawn/ykoath](https://github.com/yawn/ykoath). To allow this to be built directly from this repo, the fork is added as a git submodule. This can be cloned using `git clone --recurse-submodules` or cloning normally and then executing `git submodule update --init`. Once the change has been merged upstream, the submodule and this notice will go away.
* Requires some to be installed with a package manager
* There is no way to input your key password through the UI yet. Do that with `make set-password` and then it should work fine.
* Error handling is terrible right now. If things don't work, check the debug log in Alfred
## Building
## Installation
Building requires [`mage`](https://magefile.org/)
Clone this repo
To see all targets and their descriptions, run `mage -l`. The most basic ones are as follows:
```bash
git clone https://git.iamthefij.com/iamthefij/alfred-yubico-auth.git
```
* `mage install`: Build and install into your local machine for testing
* `mage dist`: Build bundle for distribution
Either install your dependencies manually or, if you have MacPorts, you can use:
```bash
make install-ports
```
Otherwise you need to install `swig swig-python ykpers libu2f-host libusb` some other way.
Finally up the virtualenv and install to your Alfred with
```bash
make install
```
## Credits
This uses [deanishe/awgo](https://github.com/deanishe/awgo) to interface with Alfred and [yawn/ykoath](https://github.com/yawn/ykoath) for interracting with the Yubikey
Uses the amazing [deanishe/alfred-workflow](https://github.com/deanishe/alfred-workflow) package

0
alfred_yauth/__init__.py Normal file
View File

235
alfred_yauth/controller.py Normal file
View File

@ -0,0 +1,235 @@
import hashlib
from binascii import a2b_hex
from binascii import b2a_hex
from ykman.descriptor import get_descriptors
from ykman.driver_ccid import APDUError
from ykman.driver_otp import YkpersError
from ykman.oath import Credential
from ykman.oath import OathController
from ykman.oath import SW
from ykman.util import CAPABILITY
from ykman.util import derive_key
from ykman.util import parse_b32_key
from ykman.util import TRANSPORT
NON_FEATURE_CAPABILITIES = [CAPABILITY.CCID, CAPABILITY.NFC]
class DeviceNotFoundError(Exception):
pass
class CouldNotOpenDeviceError(Exception):
pass
class Controller(object):
_descriptor = None
_dev_info = None
def get_features(self):
return [
c.name for c in CAPABILITY if c not in NON_FEATURE_CAPABILITIES]
def count_devices(self):
return len(list(get_descriptors()))
def refresh(self):
descriptors = list(get_descriptors())
if len(descriptors) != 1:
self._descriptor = None
raise DeviceNotFoundError()
return
desc = descriptors[0]
if desc.fingerprint != (
self._descriptor.fingerprint if self._descriptor else None):
dev = desc.open_device()
if not dev:
raise CouldNotOpenDeviceError()
return
self._descriptor = desc
self._dev_info = {
'name': dev.device_name,
'version': '.'.join(str(x) for x in dev.version),
'serial': dev.serial or '',
'enabled': [c.name for c in CAPABILITY if c & dev.enabled],
'connections': [
t.name for t in TRANSPORT if t & dev.capabilities],
}
return self._dev_info
def refresh_credentials(self, timestamp, password_key=None):
return [
c.to_dict() for c in self._calculate_all(timestamp, password_key)]
def calculate(self, credential, timestamp, password_key):
return self._calculate(
Credential.from_dict(
credential), timestamp, password_key).to_dict()
def calculate_slot_mode(self, slot, digits, timestamp):
dev = self._descriptor.open_device(TRANSPORT.OTP)
code = dev.driver.calculate(
slot, challenge=timestamp, totp=True, digits=int(digits),
wait_for_touch=True)
return Credential(
self._slot_name(slot), code=code, oath_type='totp', touch=True,
algo='SHA1', expiration=self._expiration(timestamp)).to_dict()
def refresh_slot_credentials(self, slots, digits, timestamp):
result = []
if slots[0]:
cred = self._read_slot_cred(1, digits[0], timestamp)
if cred:
result.append(cred)
if slots[1]:
cred = self._read_slot_cred(2, digits[1], timestamp)
if cred:
result.append(cred)
return [c.to_dict() for c in result]
def _read_slot_cred(self, slot, digits, timestamp):
try:
dev = self._descriptor.open_device(TRANSPORT.OTP)
code = dev.driver.calculate(
slot, challenge=timestamp, totp=True, digits=int(digits),
wait_for_touch=False)
return Credential(
self._slot_name(slot), code=code, oath_type='totp',
touch=False, algo='SHA1',
expiration=self._expiration(timestamp))
except YkpersError as e:
if e.errno == 11:
return Credential(
self._slot_name(slot), oath_type='totp', touch=True,
algo='SHA1')
except:
pass
return None
def _slot_name(self, slot):
return "YubiKey Slot {}".format(slot)
def _expiration(self, timestamp):
return ((timestamp + 30) // 30) * 30
def needs_validation(self):
try:
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
return controller.locked
except:
return False
def get_oath_id(self):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
return b2a_hex(controller.id).decode('utf-8')
def derive_key(self, password):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
key = derive_key(controller.id, password)
return b2a_hex(key).decode('utf-8')
def validate(self, key):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
if key is not None:
try:
controller.validate(a2b_hex(key))
return True
except:
return False
def set_password(self, new_password, password_key):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
if controller.locked and password_key is not None:
controller.validate(a2b_hex(password_key))
if new_password is not None:
key = derive_key(controller.id, new_password)
controller.set_password(key)
else:
controller.clear_password()
def add_credential(
self, name, key, oath_type, digits, algo, touch, password_key):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
if controller.locked and password_key is not None:
controller.validate(a2b_hex(password_key))
try:
key = parse_b32_key(key)
except Exception as e:
return str(e)
try:
controller.put(
key, name, oath_type, digits, algo=algo, require_touch=touch)
except APDUError as e:
# NEO doesn't return a no space error if full,
# but a command aborted error. Assume it's because of
# no space in this context.
if e.sw == SW.NO_SPACE or e.sw == SW.COMMAND_ABORTED:
return 'No space'
else:
raise
def add_slot_credential(self, slot, key, touch):
dev = self._descriptor.open_device(TRANSPORT.OTP)
key = parse_b32_key(key)
if len(key) > 64: # Keys longer than 64 bytes are hashed.
key = hashlib.sha1(key).digest()
if len(key) > 20:
raise ValueError(
'YubiKey Slots cannot handle TOTP keys over 20 bytes.')
key += b'\x00' * (20 - len(key)) # Keys must be padded to 20 bytes.
dev.driver.program_chalresp(int(slot), key, touch)
def delete_slot_credential(self, slot):
dev = self._descriptor.open_device(TRANSPORT.OTP)
dev.driver.zap_slot(slot)
def delete_credential(self, credential, password_key):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
if controller.locked and password_key is not None:
controller.validate(a2b_hex(password_key))
controller.delete(Credential.from_dict(credential))
def _calculate(self, credential, timestamp, password_key):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
if controller.locked and password_key is not None:
controller.validate(a2b_hex(password_key))
cred = controller.calculate(credential, timestamp)
return cred
def _calculate_all(self, timestamp, password_key):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
if controller.locked and password_key is not None:
controller.validate(a2b_hex(password_key))
creds = controller.calculate_all(timestamp)
creds = [c for c in creds if not c.hidden]
return creds
def reset(self):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
controller.reset()
def slot_status(self):
dev = self._descriptor.open_device(TRANSPORT.OTP)
return list(dev.driver.slot_status)
def list_credentials(self, password_key):
dev = self._descriptor.open_device(TRANSPORT.CCID)
controller = OathController(dev.driver)
if controller.locked and password_key is not None:
controller.validate(a2b_hex(password_key))
creds = controller.list()
return [c.to_dict() for c in creds]

168
alfred_yauth/main.py Normal file
View File

@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
import sys
from getpass import getpass
from time import time
from controller import APDUError
from controller import Controller
from controller import DeviceNotFoundError
from workflow import ICON_ACCOUNT
from workflow import ICON_ERROR
from workflow import Workflow3
YUBIKEY_CREDS_KEYCHAIN = 'yubico-auth-creds'
def cred_to_item_kwargs(cred):
if cred.get('hidden'):
return None
return {
'icon': ICON_ACCOUNT,
'title': cred['name'],
'subtitle': 'Copy to clipboard',
'copytext': cred['code'],
'arg': cred['code'],
'valid': True,
}
class YubicoAuth(Workflow3):
_controller = None
def get_controller(self):
if not self._controller:
self._controller = Controller()
self._controller.refresh()
return self._controller
def ask_yubikey_password(self):
"""Prompts the user for their Yubikey password and stores it"""
self.logger.debug('Set password')
password_key = self.get_controller().derive_key(
getpass('Yubikey Password:')
)
self.save_password(YUBIKEY_CREDS_KEYCHAIN, password_key)
self.get_controller().refresh_credentials(time(), password_key)
self.add_item(
'Yubikey password set successfully',
'',
icon=ICON_ACCOUNT,
)
def get_yubikey_password(self):
"""Returns stored Yubikey password from keychain"""
return self.get_password(YUBIKEY_CREDS_KEYCHAIN)
def _get_positional_arg(self, position):
"""Safely return a positional argument"""
if len(self.args) > position:
return self.args[position]
return None
def get_command(self):
"""Get command out of the args as first parameter"""
return self._get_positional_arg(0)
def get_query(self):
"""Get query out of the args after first parameter"""
if len(self.args) < 2:
return None
return ' '.join(self.args[1:])
def _validate(self, command):
"""Validates that we can handle the current command"""
# if self.get_api_key() is None:
# self.add_item(
# title='Missing API key',
# subtitle='Set variable in settings',
# icon=ICON_ACCOUNT,
# valid=False,
# )
# return False
# if command == COMMAND_LOVE and self.get_recipient() is None:
# self.add_item(
# title='Recipient is required',
# icon=ICON_ERROR,
# valid=False,
# )
# return False
return True
def _add_cred_to_results(self, cred):
self.logger.debug('Read {}'.format(cred.get('name')))
item_args = cred_to_item_kwargs(cred)
if item_args:
self.add_item(**item_args)
def list_credentials(self):
password_key = self.get_yubikey_password()
for cred in self.get_controller().list_credentials(password_key):
self._add_cred_to_results(cred)
def refresh_credentials(self):
key = self.get_yubikey_password()
for cred in self.get_controller().refresh_credentials(time(), key):
self._add_cred_to_results(cred)
def main(self):
self.logger.debug('Starting...')
command = self.get_command()
if not self._validate(command):
self.send_feedback()
return
command_action = None
if command == 'set-password':
command_action = self.ask_yubikey_password
elif command == 'list':
command_action = self.list_credentials
else:
command_action = self.refresh_credentials
try:
command_action()
except DeviceNotFoundError:
self.add_item(
'Could not find device',
'Is your Yubikey plugged in?',
icon=ICON_ERROR,
)
except APDUError:
self.add_item(
'Could not communicate with device',
'Is your Yubikey password set correctly?',
icon=ICON_ERROR,
)
self.send_feedback()
def no_wf():
controller = Controller()
print(controller.get_features())
print(controller.count_devices())
print(controller.refresh())
password = getpass('YubiKey password?')
password_key = controller.derive_key(password)
timestamp = time()
print(controller.refresh_credentials(timestamp, password_key))
creds = controller.list_credentials(password_key)
print(creds)
def main(wf=None):
if wf is None:
no_wf()
else:
wf.main()
if __name__ == '__main__':
# main()
wf = YubicoAuth()
sys.exit(wf.run(main))

1
alfred_yauth/version Normal file
View File

@ -0,0 +1 @@
0.0.1

27
go.mod
View File

@ -1,27 +0,0 @@
module git.iamthefij.com/iamthefij/alfred-yubico-auth
go 1.20
// Right now requires https://github.com/vividboarder/ykoath branch: validate
replace github.com/yawn/ykoath => ./ykoath
// Right now requires https://github.com/iamthefij/awgo branch: alfred-5
replace github.com/deanishe/awgo => github.com/iamthefij/awgo v0.29.1-pre1
require (
git.iamthefij.com/iamthefij/slog v1.0.0
github.com/deanishe/awgo v0.29.1
github.com/magefile/mage v1.14.0
github.com/yawn/ykoath v1.0.4
)
require (
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/ebfe/scard v0.0.0-20190212122703-c3d1b1916a95 // indirect
github.com/pkg/errors v0.8.1 // indirect
go.deanishe.net/env v0.5.1 // indirect
go.deanishe.net/fuzzy v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 // indirect
golang.org/x/text v0.8.0 // indirect
howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect
)

61
go.sum
View File

@ -1,61 +0,0 @@
git.iamthefij.com/iamthefij/slog v1.0.0 h1:S+njoK+dr5VUYSopISHm2QMq3IwrHfwmi/CrAmhXVbg=
git.iamthefij.com/iamthefij/slog v1.0.0/go.mod h1:1RUj4hcCompZkAxXCRfUX786tb3cM/Zpkn97dGfUfbg=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebfe/scard v0.0.0-20190212122703-c3d1b1916a95 h1:OM0MnUcXBysj7ZtXvThVWHMoahuKQ8FuwIdeSLcNdP4=
github.com/ebfe/scard v0.0.0-20190212122703-c3d1b1916a95/go.mod h1:8hHvF8DlEq5kE3KWOsZQezdWq1OTOVxZArZMscS954E=
github.com/iamthefij/awgo v0.29.1-pre1 h1:QrkVt0y3axBJl4tfdwHrsWXbDN7lsJKHNpqtp2WJOiA=
github.com/iamthefij/awgo v0.29.1-pre1/go.mod h1:1yGF+uQfWXX99TiDfAYYKjJpHTq5lHEmvHFEVCHo6KA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.deanishe.net/env v0.5.1 h1:WiOncK5uJj8Um57Vj2dc1bq1lMN7fgRag9up7I3LZy0=
go.deanishe.net/env v0.5.1/go.mod h1:ihEYfDm0K0hq3f5ACTCQDrMTWxH9fTiA1lh1i0aMqm0=
go.deanishe.net/fuzzy v1.0.0 h1:3Qp6PCX0DLb9z03b5OHwAGsbRSkgJpSLncsiDdXDt4Y=
go.deanishe.net/fuzzy v1.0.0/go.mod h1:2yEEMfG7jWgT1s5EO0TteVWmx2MXFBRMr5cMm84bQNY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v0.0.0-20201203080718-1454fab16a06 h1:QDxUo/w2COstK1wIBYpzQlHX/NqaQTcf9jyz347nI58=
howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=

View File

@ -3,100 +3,14 @@
<plist version="1.0">
<dict>
<key>bundleid</key>
<string>com.iamthefij.alfred-yubico-auth</string>
<string>com.vividboarder.alfred-yubico-auth</string>
<key>connections</key>
<dict>
<key>0149DE47-0232-434D-BB9E-B2C0B419A2E3</key>
<array>
<dict>
<key>destinationuid</key>
<string>55DFD4B6-922A-4259-8625-EC14889FBACD</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>27BC434A-1663-44A7-85AF-C4AC6E1BEFB7</key>
<array>
<dict>
<key>destinationuid</key>
<string>2D69982A-0DB6-4ABA-812F-C7F2A73650AE</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>2D69982A-0DB6-4ABA-812F-C7F2A73650AE</key>
<array>
<dict>
<key>destinationuid</key>
<string>A8D2CCAC-5CA4-495E-BB62-5C7F596FA157</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>sourceoutputuid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
<key>vitoclose</key>
<false/>
</dict>
<dict>
<key>destinationuid</key>
<string>0149DE47-0232-434D-BB9E-B2C0B419A2E3</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>55DFD4B6-922A-4259-8625-EC14889FBACD</key>
<array>
<dict>
<key>destinationuid</key>
<string>AD82ED59-033E-4860-B371-8128574E2FBC</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>sourceoutputuid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
<key>vitoclose</key>
<false/>
</dict>
<dict>
<key>destinationuid</key>
<string>506787F2-9A61-492C-8C49-30EE04FB70BC</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>8486DCAA-AFB7-407D-A0E9-E57E09997B24</key>
<array>
<dict>
<key>destinationuid</key>
<string>DB934647-74DE-4182-ACDF-93C629AA99D9</string>
<key>modifiers</key>
<integer>524288</integer>
<key>modifiersubtext</key>
<string>Show</string>
<key>vitoclose</key>
<false/>
</dict>
<dict>
<key>destinationuid</key>
<string>2D69982A-0DB6-4ABA-812F-C7F2A73650AE</string>
<string>9F48DDE6-BBE7-42ED-ABE5-C9255C92F1CD</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
@ -106,11 +20,11 @@
</dict>
<dict>
<key>destinationuid</key>
<string>27BC434A-1663-44A7-85AF-C4AC6E1BEFB7</string>
<string>FB0DDF80-FF90-439A-BF3F-6EC58C2AA870</string>
<key>modifiers</key>
<integer>1048576</integer>
<key>modifiersubtext</key>
<string>Paste code</string>
<string>Paste token</string>
<key>vitoclose</key>
<false/>
</dict>
@ -128,56 +42,6 @@
<false/>
</dict>
</array>
<key>A8D2CCAC-5CA4-495E-BB62-5C7F596FA157</key>
<array>
<dict>
<key>destinationuid</key>
<string>DA99BA2E-7234-491D-BD0F-044151FA98E2</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>AD82ED59-033E-4860-B371-8128574E2FBC</key>
<array>
<dict>
<key>destinationuid</key>
<string>F3AEDAF9-44BC-4E39-B908-724974ACA17B</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>sourceoutputuid</key>
<string>7B2EFCB5-ED69-4B10-84C5-4F6320C91491</string>
<key>vitoclose</key>
<false/>
</dict>
<dict>
<key>destinationuid</key>
<string>FB0DDF80-FF90-439A-BF3F-6EC58C2AA870</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>sourceoutputuid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
<key>vitoclose</key>
<false/>
</dict>
<dict>
<key>destinationuid</key>
<string>9F48DDE6-BBE7-42ED-ABE5-C9255C92F1CD</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>BFB3A122-52BB-4FF1-B5B3-CECD42A730DB</key>
<array>
<dict>
@ -204,44 +68,6 @@
<false/>
</dict>
</array>
<key>DA99BA2E-7234-491D-BD0F-044151FA98E2</key>
<array>
<dict>
<key>destinationuid</key>
<string>C252A5EC-1AEE-4EF4-864F-67483EAADCFA</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>sourceoutputuid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
<key>vitoclose</key>
<false/>
</dict>
<dict>
<key>destinationuid</key>
<string>506787F2-9A61-492C-8C49-30EE04FB70BC</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>DB934647-74DE-4182-ACDF-93C629AA99D9</key>
<array>
<dict>
<key>destinationuid</key>
<string>2D69982A-0DB6-4ABA-812F-C7F2A73650AE</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>E86DC7C1-35C1-4BA9-8B33-A95DE7082F7E</key>
<array>
<dict>
@ -255,22 +81,9 @@
<false/>
</dict>
</array>
<key>FB0DDF80-FF90-439A-BF3F-6EC58C2AA870</key>
<array>
<dict>
<key>destinationuid</key>
<string>E86DC7C1-35C1-4BA9-8B33-A95DE7082F7E</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
</dict>
<key>createdby</key>
<string>Ian Fijolek</string>
<string>ViViDboarder</string>
<key>description</key>
<string>2FA for Yubikeys</string>
<key>disabled</key>
@ -282,225 +95,20 @@
<dict>
<key>config</key>
<dict>
<key>argument</key>
<key>autopaste</key>
<false/>
<key>clipboardtext</key>
<string>{query}</string>
<key>passthroughargument</key>
<false/>
<key>variables</key>
<dict>
<key>result_action</key>
<string>show</string>
</dict>
<key>transient</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.utility.argument</string>
<string>alfred.workflow.output.clipboard</string>
<key>uid</key>
<string>DB934647-74DE-4182-ACDF-93C629AA99D9</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>lastpathcomponent</key>
<false/>
<key>onlyshowifquerypopulated</key>
<false/>
<key>removeextension</key>
<false/>
<key>text</key>
<string>Password key is now stored in your keychain</string>
<key>title</key>
<string>Password saved</string>
</dict>
<key>type</key>
<string>alfred.workflow.output.notification</string>
<key>uid</key>
<string>C252A5EC-1AEE-4EF4-864F-67483EAADCFA</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>concurrently</key>
<false/>
<key>escaping</key>
<integer>102</integer>
<key>script</key>
<string>./alfred-yubico-auth -run-script set-password</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string></string>
<key>type</key>
<integer>0</integer>
</dict>
<key>type</key>
<string>alfred.workflow.action.script</string>
<key>uid</key>
<string>A8D2CCAC-5CA4-495E-BB62-5C7F596FA157</string>
<string>9F48DDE6-BBE7-42ED-ABE5-C9255C92F1CD</string>
<key>version</key>
<integer>2</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>alfredfiltersresults</key>
<true/>
<key>alfredfiltersresultsmatchmode</key>
<integer>0</integer>
<key>argumenttreatemptyqueryasnil</key>
<false/>
<key>argumenttrimmode</key>
<integer>0</integer>
<key>argumenttype</key>
<integer>1</integer>
<key>escaping</key>
<integer>0</integer>
<key>keyword</key>
<string>yubikey</string>
<key>queuedelaycustom</key>
<integer>3</integer>
<key>queuedelayimmediatelyinitially</key>
<true/>
<key>queuedelaymode</key>
<integer>0</integer>
<key>queuemode</key>
<integer>1</integer>
<key>runningsubtext</key>
<string></string>
<key>script</key>
<string>./alfred-yubico-auth list</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string></string>
<key>subtext</key>
<string>Get 2FA tokens from Yubikey</string>
<key>title</key>
<string>Yubikey 2FA</string>
<key>type</key>
<integer>0</integer>
<key>withspace</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.input.scriptfilter</string>
<key>uid</key>
<string>8486DCAA-AFB7-407D-A0E9-E57E09997B24</string>
<key>version</key>
<integer>3</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>conditions</key>
<array>
<dict>
<key>inputstring</key>
<string>{var:result}</string>
<key>matchcasesensitive</key>
<false/>
<key>matchmode</key>
<integer>0</integer>
<key>matchstring</key>
<string>success</string>
<key>outputlabel</key>
<string>success</string>
<key>uid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
</dict>
</array>
<key>elselabel</key>
<string>else</string>
</dict>
<key>type</key>
<string>alfred.workflow.utility.conditional</string>
<key>uid</key>
<string>DA99BA2E-7234-491D-BD0F-044151FA98E2</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>conditions</key>
<array>
<dict>
<key>inputstring</key>
<string>{var:action}</string>
<key>matchcasesensitive</key>
<false/>
<key>matchmode</key>
<integer>0</integer>
<key>matchstring</key>
<string>set-password</string>
<key>outputlabel</key>
<string>Set password</string>
<key>uid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
</dict>
</array>
<key>elselabel</key>
<string>else</string>
</dict>
<key>type</key>
<string>alfred.workflow.utility.conditional</string>
<key>uid</key>
<string>2D69982A-0DB6-4ABA-812F-C7F2A73650AE</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>argument</key>
<string>{query}</string>
<key>passthroughargument</key>
<false/>
<key>variables</key>
<dict>
<key>result_action</key>
<string>paste</string>
</dict>
</dict>
<key>type</key>
<string>alfred.workflow.utility.argument</string>
<key>uid</key>
<string>27BC434A-1663-44A7-85AF-C4AC6E1BEFB7</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>alignment</key>
<integer>0</integer>
<key>backgroundcolor</key>
<string></string>
<key>fadespeed</key>
<integer>0</integer>
<key>fillmode</key>
<integer>0</integer>
<key>font</key>
<string></string>
<key>ignoredynamicplaceholders</key>
<false/>
<key>largetypetext</key>
<string>{query}</string>
<key>textcolor</key>
<string></string>
<key>wrapat</key>
<integer>50</integer>
</dict>
<key>type</key>
<string>alfred.workflow.output.largetype</string>
<key>uid</key>
<string>F3AEDAF9-44BC-4E39-B908-724974ACA17B</string>
<key>version</key>
<integer>3</integer>
</dict>
<dict>
<key>config</key>
<dict>
@ -522,69 +130,6 @@
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>conditions</key>
<array>
<dict>
<key>inputstring</key>
<string>{var:result_action}</string>
<key>matchcasesensitive</key>
<false/>
<key>matchmode</key>
<integer>0</integer>
<key>matchstring</key>
<string>show</string>
<key>outputlabel</key>
<string>show</string>
<key>uid</key>
<string>7B2EFCB5-ED69-4B10-84C5-4F6320C91491</string>
</dict>
<dict>
<key>inputstring</key>
<string>{var:result_action}</string>
<key>matchcasesensitive</key>
<false/>
<key>matchmode</key>
<integer>0</integer>
<key>matchstring</key>
<string>paste</string>
<key>outputlabel</key>
<string>paste</string>
<key>uid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
</dict>
</array>
<key>elselabel</key>
<string>copy</string>
</dict>
<key>type</key>
<string>alfred.workflow.utility.conditional</string>
<key>uid</key>
<string>AD82ED59-033E-4860-B371-8128574E2FBC</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>autopaste</key>
<true/>
<key>clipboardtext</key>
<string>{query}</string>
<key>ignoredynamicplaceholders</key>
<false/>
<key>transient</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.output.clipboard</string>
<key>uid</key>
<string>FB0DDF80-FF90-439A-BF3F-6EC58C2AA870</string>
<key>version</key>
<integer>3</integer>
</dict>
<dict>
<key>config</key>
<dict>
@ -592,8 +137,6 @@
<false/>
<key>clipboardtext</key>
<string></string>
<key>ignoredynamicplaceholders</key>
<false/>
<key>transient</key>
<false/>
</dict>
@ -602,95 +145,8 @@
<key>uid</key>
<string>BFB3A122-52BB-4FF1-B5B3-CECD42A730DB</string>
<key>version</key>
<integer>3</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>concurrently</key>
<false/>
<key>escaping</key>
<integer>102</integer>
<key>script</key>
<string>query=$1
./alfred-yubico-auth -run-script "$query"</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string></string>
<key>type</key>
<integer>0</integer>
</dict>
<key>type</key>
<string>alfred.workflow.action.script</string>
<key>uid</key>
<string>0149DE47-0232-434D-BB9E-B2C0B419A2E3</string>
<key>version</key>
<integer>2</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>seconds</key>
<string>10</string>
</dict>
<key>type</key>
<string>alfred.workflow.utility.delay</string>
<key>uid</key>
<string>E86DC7C1-35C1-4BA9-8B33-A95DE7082F7E</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>conditions</key>
<array>
<dict>
<key>inputstring</key>
<string>{var:result}</string>
<key>matchcasesensitive</key>
<false/>
<key>matchmode</key>
<integer>0</integer>
<key>matchstring</key>
<string>success</string>
<key>outputlabel</key>
<string>success</string>
<key>uid</key>
<string>94F60406-01FF-4991-A697-2C83147293EB</string>
</dict>
</array>
<key>elselabel</key>
<string>else</string>
</dict>
<key>type</key>
<string>alfred.workflow.utility.conditional</string>
<key>uid</key>
<string>55DFD4B6-922A-4259-8625-EC14889FBACD</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>autopaste</key>
<false/>
<key>clipboardtext</key>
<string>{query}</string>
<key>ignoredynamicplaceholders</key>
<false/>
<key>transient</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.output.clipboard</string>
<key>uid</key>
<string>9F48DDE6-BBE7-42ED-ABE5-C9255C92F1CD</string>
<key>version</key>
<integer>3</integer>
</dict>
<dict>
<key>config</key>
<dict>
@ -715,158 +171,137 @@
<dict>
<key>config</key>
<dict>
<key>lastpathcomponent</key>
<false/>
<key>onlyshowifquerypopulated</key>
<key>alfredfiltersresults</key>
<true/>
<key>removeextension</key>
<false/>
<key>text</key>
<string>{query}</string>
<key>alfredfiltersresultsmatchmode</key>
<integer>0</integer>
<key>argumenttrimmode</key>
<integer>0</integer>
<key>argumenttype</key>
<integer>1</integer>
<key>escaping</key>
<integer>0</integer>
<key>keyword</key>
<string>yubikey</string>
<key>queuedelaycustom</key>
<integer>3</integer>
<key>queuedelayimmediatelyinitially</key>
<true/>
<key>queuedelaymode</key>
<integer>0</integer>
<key>queuemode</key>
<integer>1</integer>
<key>runningsubtext</key>
<string></string>
<key>script</key>
<string>./venv/bin/python -m alfred_yauth.main</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string>main.py</string>
<key>subtext</key>
<string>Get 2FA tokens from Yubikey</string>
<key>title</key>
<string>Alfred Yubikey Error</string>
<string>Yubikey 2FA</string>
<key>type</key>
<integer>0</integer>
<key>withspace</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.output.notification</string>
<string>alfred.workflow.input.scriptfilter</string>
<key>uid</key>
<string>506787F2-9A61-492C-8C49-30EE04FB70BC</string>
<string>8486DCAA-AFB7-407D-A0E9-E57E09997B24</string>
<key>version</key>
<integer>2</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>seconds</key>
<string>10</string>
</dict>
<key>type</key>
<string>alfred.workflow.utility.delay</string>
<key>uid</key>
<string>E86DC7C1-35C1-4BA9-8B33-A95DE7082F7E</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>autopaste</key>
<true/>
<key>clipboardtext</key>
<string>{query}</string>
<key>transient</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.output.clipboard</string>
<key>uid</key>
<string>FB0DDF80-FF90-439A-BF3F-6EC58C2AA870</string>
<key>version</key>
<integer>2</integer>
</dict>
</array>
<key>readme</key>
<string></string>
<key>uidata</key>
<dict>
<key>0149DE47-0232-434D-BB9E-B2C0B419A2E3</key>
<dict>
<key>xpos</key>
<integer>180</integer>
<key>ypos</key>
<integer>305</integer>
</dict>
<key>0718204D-3398-4AEF-A621-DDDE1FC6ED75</key>
<dict>
<key>xpos</key>
<integer>1245</integer>
<integer>960</integer>
<key>ypos</key>
<integer>225</integer>
</dict>
<key>27BC434A-1663-44A7-85AF-C4AC6E1BEFB7</key>
<dict>
<key>xpos</key>
<integer>260</integer>
<key>ypos</key>
<integer>155</integer>
</dict>
<key>2D69982A-0DB6-4ABA-812F-C7F2A73650AE</key>
<dict>
<key>xpos</key>
<integer>365</integer>
<key>ypos</key>
<integer>90</integer>
</dict>
<key>506787F2-9A61-492C-8C49-30EE04FB70BC</key>
<dict>
<key>xpos</key>
<integer>835</integer>
<key>ypos</key>
<integer>540</integer>
</dict>
<key>55DFD4B6-922A-4259-8625-EC14889FBACD</key>
<dict>
<key>xpos</key>
<integer>365</integer>
<key>ypos</key>
<integer>330</integer>
<integer>140</integer>
</dict>
<key>8486DCAA-AFB7-407D-A0E9-E57E09997B24</key>
<dict>
<key>xpos</key>
<integer>70</integer>
<integer>120</integer>
<key>ypos</key>
<integer>65</integer>
<integer>140</integer>
</dict>
<key>9F48DDE6-BBE7-42ED-ABE5-C9255C92F1CD</key>
<dict>
<key>xpos</key>
<integer>650</integer>
<integer>360</integer>
<key>ypos</key>
<integer>415</integer>
</dict>
<key>A8D2CCAC-5CA4-495E-BB62-5C7F596FA157</key>
<dict>
<key>xpos</key>
<integer>515</integer>
<key>ypos</key>
<integer>55</integer>
</dict>
<key>AD82ED59-033E-4860-B371-8128574E2FBC</key>
<dict>
<key>xpos</key>
<integer>485</integer>
<key>ypos</key>
<integer>285</integer>
<integer>140</integer>
</dict>
<key>BFB3A122-52BB-4FF1-B5B3-CECD42A730DB</key>
<dict>
<key>xpos</key>
<integer>1100</integer>
<integer>780</integer>
<key>ypos</key>
<integer>300</integer>
</dict>
<key>C252A5EC-1AEE-4EF4-864F-67483EAADCFA</key>
<dict>
<key>xpos</key>
<integer>800</integer>
<key>ypos</key>
<integer>45</integer>
<integer>140</integer>
</dict>
<key>DA3E9CE8-7C4F-4B09-BFA5-F8CA83297968</key>
<dict>
<key>xpos</key>
<integer>835</integer>
<integer>540</integer>
<key>ypos</key>
<integer>415</integer>
</dict>
<key>DA99BA2E-7234-491D-BD0F-044151FA98E2</key>
<dict>
<key>xpos</key>
<integer>670</integer>
<key>ypos</key>
<integer>75</integer>
</dict>
<key>DB934647-74DE-4182-ACDF-93C629AA99D9</key>
<dict>
<key>xpos</key>
<integer>260</integer>
<key>ypos</key>
<integer>40</integer>
<integer>140</integer>
</dict>
<key>E86DC7C1-35C1-4BA9-8B33-A95DE7082F7E</key>
<dict>
<key>xpos</key>
<integer>1015</integer>
<integer>700</integer>
<key>ypos</key>
<integer>330</integer>
</dict>
<key>F3AEDAF9-44BC-4E39-B908-724974ACA17B</key>
<dict>
<key>xpos</key>
<integer>655</integer>
<key>ypos</key>
<integer>160</integer>
<integer>170</integer>
</dict>
<key>FB0DDF80-FF90-439A-BF3F-6EC58C2AA870</key>
<dict>
<key>xpos</key>
<integer>650</integer>
<integer>360</integer>
<key>ypos</key>
<integer>285</integer>
<integer>290</integer>
</dict>
</dict>
<key>version</key>
<string>1.1.1</string>
<string>0.0.1</string>
<key>webaddress</key>
<string></string>
</dict>

View File

@ -1,146 +0,0 @@
//go:build mage
package main
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/deanishe/awgo/util"
"github.com/deanishe/awgo/util/build"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
const (
buildDir = "./build"
distDir = "./dist"
binName = "alfred-yubico-auth"
)
var (
info *build.Info
// Default mage target
Default = Run
// Output binary path
binPath = filepath.Join(buildDir, binName)
)
func InfoWithVersion(v int) build.Option {
return func(i *build.Info) {
i.AlfredMajorVersion = v
}
}
func init() {
var err error
if info, err = build.NewInfo(InfoWithVersion(5)); err != nil {
panic(err)
}
}
// Build workflow.
func Build() error {
mg.Deps(cleanBuild)
fmt.Println("Building...")
if err := sh.RunWith(info.Env(), "go", "build", "-o", binPath, "."); err != nil {
return fmt.Errorf("error building binary %w", err)
}
globs := build.Globs(
"*.png",
"info.plist",
"README.md",
"LICENSE.txt",
"password-prompt.js",
)
return build.SymlinkGlobs(buildDir, globs...)
}
// Run workflow.
func Run() error {
mg.Deps(Build)
fmt.Println("Running...")
return sh.RunWith(info.Env(), binPath)
}
// Dist packages workflow for distribution.
func Dist() error {
mg.SerialDeps(Clean, Build)
fmt.Println("Exporting dist...")
p, err := build.Export(buildDir, distDir)
if err != nil {
return err
}
fmt.Printf("Exported %q\n", p)
return nil
}
// Install symlinked workflow to Alfred.
func Install() error {
mg.Deps(Build)
fmt.Printf("Installing (linking) %q to %q...\n", buildDir, info.InstallDir)
if err := sh.Rm(info.InstallDir); err != nil {
return fmt.Errorf("error cleaning previously installed workflow: %w", err)
}
return build.Symlink(info.InstallDir, buildDir, true)
}
// InstallHooks will install pre-commit hooks.
func InstallHooks() error {
return sh.RunV("pre-commit", "install", "--overwrite", "--install-hooks")
}
// Check will run all pre-commit hooks.
func Check() error {
return sh.RunV("pre-commit", "run", "--all-files")
}
// Clean build files.
func Clean() error {
fmt.Println("Cleaning...")
mg.Deps(cleanBuild, cleanMage)
return nil
}
// DistClean build files and distribution files.
func DistClean() error {
mg.Deps(Clean, cleanDist)
return nil
}
func cleanDir(name string) error {
if !util.PathExists(name) {
return nil
}
infos, err := ioutil.ReadDir(name)
if err != nil {
return fmt.Errorf("cleanDir could not read folder: %w", err)
}
for _, fi := range infos {
if err := sh.Rm(filepath.Join(name, fi.Name())); err != nil {
return fmt.Errorf("cleanDir could not remove file: %w", err)
}
}
return nil
}
func cleanBuild() error { return cleanDir(buildDir) }
func cleanDist() error { return cleanDir(distDir) }
func cleanMage() error { return sh.Run("mage", "-clean") }

179
main.go
View File

@ -1,179 +0,0 @@
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"git.iamthefij.com/iamthefij/slog"
aw "github.com/deanishe/awgo"
"github.com/deanishe/awgo/util"
"github.com/yawn/ykoath"
)
var (
wf *aw.Workflow
oath *ykoath.OATH
keychainAccount = "yubico-auth-creds"
errIncorrectPassword = errors.New("incorrect password")
)
func init() {
wf = aw.New()
}
func main() {
wf.Run(run)
}
func promptPassword() (string, error) {
out, err := util.Run("./password-prompt.js")
if err != nil {
return "", fmt.Errorf("error reading password from prompt: %w", err)
}
out = bytes.TrimRight(out, "\n")
return string(out), nil
}
func setPassword(s *ykoath.Select) error {
passphrase, err := promptPassword()
if err != nil {
return fmt.Errorf("failed reading passphrase: %w", err)
}
err = validatePassphrase(s, passphrase)
if err != nil {
return fmt.Errorf("failed validating passphrase: %w", err)
}
err = wf.Keychain.Set(keychainAccount, passphrase)
if err != nil {
return fmt.Errorf("failed storing passphrase in keychain: %w", err)
}
return nil
}
func sendResult(result string, args ...string) error {
results := aw.NewArgVars()
results.Arg(args...)
results.Var("result", result)
return results.Send()
}
func validatePassphrase(s *ykoath.Select, passphrase string) error {
key := s.DeriveKey(passphrase)
// verify password is correct with a validate call
ok, err := oath.Validate(s, key)
if err != nil {
return fmt.Errorf("error in validate: %w", err)
}
if !ok {
return errIncorrectPassword
}
return nil
}
func run() {
runScript := flag.Bool("run-script", false, "change output to script output")
wf.Args()
flag.Parse()
if *runScript {
wf.Configure(aw.TextErrors(true))
}
var err error
oath, err = ykoath.New()
if err != nil {
wf.FatalError(fmt.Errorf("failed to iniatialize new oath: %w", err))
}
defer oath.Close()
oath.Debug = slog.Debug
// Select oath to begin
s, err := oath.Select()
if err != nil {
wf.FatalError(fmt.Errorf("failed to select oath: %w", err))
}
// Check to see if we are trying to set a password
if flag.Arg(0) == "set-password" {
err = setPassword(s)
if err != nil {
wf.FatalError(fmt.Errorf("failed to set password: %w", err))
}
if err = sendResult("success"); err != nil {
wf.FatalError(fmt.Errorf("failed to send password set result: %w", err))
}
return
}
// If required, authenticate with password from keychain
if s.Challenge != nil {
passphrase, err := wf.Keychain.Get(keychainAccount)
if err != nil {
slog.Error("no key found in keychain but password is required")
wf.NewWarningItem("No password set", "↵ to set password").
Var("action", "set-password").
Valid(true)
wf.SendFeedback()
return
}
err = validatePassphrase(s, passphrase)
if err != nil {
wf.FatalError(fmt.Errorf("passphrase failed: %w", err))
}
}
if flag.Arg(0) == "list" {
// List names only
names, err := oath.List()
if err != nil {
wf.FatalError(fmt.Errorf("failed to list names: %w", err))
}
for _, name := range names {
slog.Log(name.Name)
wf.NewItem(name.Name).
Icon(aw.IconAccount).
Subtitle("Copy to clipboard").
Arg(name.Name).
Valid(true)
}
} else {
name := flag.Arg(0)
code, err := oath.CalculateOne(name)
if err != nil {
// TODO: Check for error "requires-auth" and notify touch
wf.FatalError(fmt.Errorf("failed to generate code: %w", err))
}
slog.Log(code)
if err = sendResult("success", code); err != nil {
wf.FatalError(fmt.Errorf("failed to send code: %w", err))
}
}
if !*runScript {
wf.SendFeedback()
}
}

View File

@ -1,18 +0,0 @@
#! /usr/bin/osascript
// https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/PromptforText.html#//apple_ref/doc/uid/TP40016239-CH80-SW1
function run(){
var app = Application.currentApplication()
app.includeStandardAdditions = true
var response = app.displayDialog(
"Enter your Yubikey passphrase",
{
defaultAnswer: "",
withIcon: "stop",
buttons: ["Cancel", "Save"],
defaultButton: "Save",
cancelButton: "Cancel",
givingUpAfter: 120,
hiddenAnswer: true
})
return response.textReturned
}

8
replace-workflow.sh Executable file
View File

@ -0,0 +1,8 @@
#! /bin/bash
set -e
echo "Warning! This will remove the workflow at the provided path and replace it with a link to this directory"
read -p "Path to workflow to replace: " existing_workflow
rm -fr "$existing_workflow"
ln -s `pwd` "$existing_workflow"

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
alfred-workflow==1.27
yubikey-manager==0.4.0

View File

@ -1,3 +1,4 @@
#! /bin/bash
echo "$1" > ./alfred_yauth/version
plutil -replace version -string "$1" ./info.plist

32
setup.py Normal file
View File

@ -0,0 +1,32 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
import os
from setuptools import find_packages
from setuptools import setup
def read_version():
version_path = 'version'
try:
return open(
os.path.join(os.path.dirname(__file__), version_path)
).read()
except:
pass
setup(
name='Yubico Auth Workflow',
version=read_version(),
description='Yubico Auth workflow for Alfred',
author='Ian Fijolek',
author_email='ian@iamthefij.com.com',
url='',
packages=find_packages(exclude=['tests*']),
install_requires=[],
license='MIT',
)

31
tox.ini Normal file
View File

@ -0,0 +1,31 @@
[tox]
envlist = py
indexserver =
default = https://pypi.python.org/simple/
[testenv]
deps = -rrequirements-dev.txt
[testenv:py]
deps = {[testenv]deps}
commands =
coverage run --source=./src/,tests/ -m pytest --strict {posargs}
coverage report -m
pre-commit run --all-files
[testenv:lint]
deps = {[testenv]deps}
flake8
commands = flake8 .
[testenv:pre-commit]
deps = pre-commit>=0.4.2
commands = pre-commit {posargs}
[flake8]
exclude = .svn,CVS,.bzr,.hg,.git,__pycache__,.ropeproject,.tox,docs,virtualenv_run
filename = *.py,*.wsgi
max-line-length = 80
[pytest]
norecursedirs = .* _darcs CVS docs virtualenv_run

1
ykoath

@ -1 +0,0 @@
Subproject commit fd081cb213d030585bfdd03305e03bff4d6e7a09