Add OnlyKey support

This commit is contained in:
onlykey
2020-09-24 13:52:14 +03:00
committed by Roman Zeyde
parent a12202d809
commit fd182e744f
12 changed files with 476 additions and 22 deletions

View File

@@ -14,7 +14,7 @@ See the following blog posts about this tool:
- [TREZOR Firmware 1.4.0GPG decryption support](https://www.reddit.com/r/TREZOR/comments/50h8r9/new_trezor_firmware_fidou2f_and_initial_ethereum/d7420q7/) - [TREZOR Firmware 1.4.0GPG decryption support](https://www.reddit.com/r/TREZOR/comments/50h8r9/new_trezor_firmware_fidou2f_and_initial_ethereum/d7420q7/)
- [A Step by Step Guide to Securing your SSH Keys with the Ledger Nano S](https://thoughts.t37.net/a-step-by-step-guide-to-securing-your-ssh-keys-with-the-ledger-nano-s-92e58c64a005) - [A Step by Step Guide to Securing your SSH Keys with the Ledger Nano S](https://thoughts.t37.net/a-step-by-step-guide-to-securing-your-ssh-keys-with-the-ledger-nano-s-92e58c64a005)
Currently [TREZOR One](https://trezor.io/), [TREZOR Model T](https://trezor.io/), [Keepkey](https://www.keepkey.com/), and [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s) are supported. Currently [TREZOR One](https://trezor.io/), [TREZOR Model T](https://trezor.io/), [Keepkey](https://www.keepkey.com/), [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s), and [OnlyKey](https://onlykey.io) are supported.
## Components ## Components
@@ -25,6 +25,7 @@ agents to interact with several different hardware devices:
* [`trezor-agent`](https://pypi.org/project/trezor-agent/): Using Trezor as hardware-based SSH/PGP agent * [`trezor-agent`](https://pypi.org/project/trezor-agent/): Using Trezor as hardware-based SSH/PGP agent
* [`ledger_agent`](https://pypi.org/project/ledger_agent/): Using Ledger as hardware-based SSH/PGP agent * [`ledger_agent`](https://pypi.org/project/ledger_agent/): Using Ledger as hardware-based SSH/PGP agent
* [`keepkey_agent`](https://pypi.org/project/keepkey_agent/): Using KeepKey as hardware-based SSH/PGP agent * [`keepkey_agent`](https://pypi.org/project/keepkey_agent/): Using KeepKey as hardware-based SSH/PGP agent
* [`onlykey-agent`](https://pypi.org/project/onlykey-agent/): Using OnlyKey as hardware-based SSH/PGP agent
The [/releases](/releases) page on Github contains the `libagent` The [/releases](/releases) page on Github contains the `libagent`

View File

@@ -0,0 +1,7 @@
import libagent.gpg
import libagent.ssh
from libagent.device.onlykey import OnlyKey as DeviceType
ssh_agent = lambda: libagent.ssh.main(DeviceType)
gpg_tool = lambda: libagent.gpg.main(DeviceType)
gpg_agent = lambda: libagent.gpg.run_agent(DeviceType)

40
agents/onlykey/setup.py Normal file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python
from setuptools import setup
setup(
name='onlykey-agent',
version='1.2.0',
description='Using onlykey as hardware SSH/GPG agent',
author='CryptoTrust',
author_email='t@crp.to',
url='http://github.com/trustcrypto/onlykey-agent',
scripts=['onlykey_agent.py'],
install_requires=[
'libagent>=0.14.2',
'onlykey>=1.2.0'
],
platforms=['POSIX'],
classifiers=[
'Environment :: Console',
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
'Operating System :: POSIX',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Networking',
'Topic :: Communications',
'Topic :: Security',
'Topic :: Utilities',
],
entry_points={'console_scripts': [
'onlykey-agent = onlykey_agent:ssh_agent',
'onlykey-gpg = onlykey_agent:gpg_tool',
'onlykey-gpg-agent = onlykey_agent:gpg_agent',
]},
)

View File

@@ -131,7 +131,28 @@ Then, install the latest [keepkey_agent](https://pypi.python.org/pypi/keepkey_ag
$ pip3 install --user -e trezor-agent/agents/ledger $ pip3 install --user -e trezor-agent/agents/ledger
``` ```
# 5. Installation Troubleshooting # 5. Install the OnlyKey agent
1. Make sure you are running the latest firmware version on your OnlyKey:
* [OnlyKey Firmware Upgrade Guide](https://docs.crp.to/upgradeguide.html)
2. Make sure that your `udev` rules are configured [correctly](https://docs.crp.to/linux.html#udev-rule).
3. Then, install the latest [onlykey-agent](https://pypi.python.org/pypi/onlykey-agent) package:
```
$ pip3 install onlykey-agent
```
Or, directly from the latest source code:
```
$ git clone https://github.com/romanz/trezor-agent
$ pip3 install --user -e trezor-agent
$ pip3 install --user -e trezor-agent/agents/onlykey
```
# 6. Installation Troubleshooting
If there is an import problem with the installed `protobuf` package, If there is an import problem with the installed `protobuf` package,
see [this issue](https://github.com/romanz/trezor-agent/issues/28) for fixing it. see [this issue](https://github.com/romanz/trezor-agent/issues/28) for fixing it.

View File

@@ -18,14 +18,14 @@ Thanks!
Run Run
``` ```
$ (trezor|keepkey|ledger)-gpg init "Roman Zeyde <roman.zeyde@gmail.com>" $ (trezor|keepkey|ledger|onlykey)-gpg init "Roman Zeyde <roman.zeyde@gmail.com>"
``` ```
Follow the instructions provided to complete the setup. Keep note of the timestamp value which you'll need if you want to regenerate the key later. Follow the instructions provided to complete the setup. Keep note of the timestamp value which you'll need if you want to regenerate the key later.
If you'd like a Trezor-style PIN entry program, follow [these instructions](README-PINENTRY.md). If you'd like a Trezor-style PIN entry program, follow [these instructions](README-PINENTRY.md).
2. Add `export GNUPGHOME=~/.gnupg/(trezor|keepkey|ledger)` to your `.bashrc` or other environment file. 2. Add `export GNUPGHOME=~/.gnupg/(trezor|keepkey|ledger|onlykey)` to your `.bashrc` or other environment file.
This `GNUPGHOME` contains your hardware keyring and agent settings. This agent software assumes all keys are backed by hardware devices so you can't use standard GPG keys in `GNUPGHOME` (if you do mix keys you'll receive an error when you attempt to use them). This `GNUPGHOME` contains your hardware keyring and agent settings. This agent software assumes all keys are backed by hardware devices so you can't use standard GPG keys in `GNUPGHOME` (if you do mix keys you'll receive an error when you attempt to use them).
@@ -203,7 +203,7 @@ Follow [these instructions](enigmail.md) to set up Enigmail in Thunderbird.
##### 1. Create these files in `~/.config/systemd/user` ##### 1. Create these files in `~/.config/systemd/user`
Replace `trezor` with `keepkey` or `ledger` as required. Replace `trezor` with `keepkey` or `ledger` or `onlykey` as required.
###### `trezor-gpg-agent.service` ###### `trezor-gpg-agent.service`

View File

@@ -4,13 +4,13 @@
SSH requires no configuration, but you may put common command line options in `~/.ssh/agent.conf` to avoid repeating them in every invocation. SSH requires no configuration, but you may put common command line options in `~/.ssh/agent.conf` to avoid repeating them in every invocation.
See `(trezor|keepkey|ledger)-agent -h` for details on supported options and the configuration file format. See `(trezor|keepkey|ledger|onlykey)-agent -h` for details on supported options and the configuration file format.
If you'd like a Trezor-style PIN entry program, follow [these instructions](README-PINENTRY.md). If you'd like a Trezor-style PIN entry program, follow [these instructions](README-PINENTRY.md).
## 2. Usage ## 2. Usage
Use the `(trezor|keepkey|ledger)-agent` program to work with SSH. It has three main modes of operation: Use the `(trezor|keepkey|ledger|onlykey)-agent` program to work with SSH. It has three main modes of operation:
##### 1. Export public keys ##### 1. Export public keys
@@ -18,7 +18,7 @@ To get your public key so you can add it to `authorized_hosts` or allow
ssh access to a service that supports it, run: ssh access to a service that supports it, run:
``` ```
(trezor|keepkey|ledger)-agent identity@myhost (trezor|keepkey|ledger|onlykey)-agent identity@myhost
``` ```
The identity (ex: `identity@myhost`) is used to derive the public key and is added as a comment to the exported key string. The identity (ex: `identity@myhost`) is used to derive the public key and is added as a comment to the exported key string.
@@ -28,7 +28,7 @@ The identity (ex: `identity@myhost`) is used to derive the public key and is add
Run Run
``` ```
$ (trezor|keepkey|ledger)-agent identity@myhost -- COMMAND --WITH --ARGUMENTS $ (trezor|keepkey|ledger|onlykey)-agent identity@myhost -- COMMAND --WITH --ARGUMENTS
``` ```
to start the agent in the background and execute the command with environment variables set up to use the SSH agent. The specified identity is used for all SSH connections. The agent will exit after the command completes. to start the agent in the background and execute the command with environment variables set up to use the SSH agent. The specified identity is used for all SSH connections. The agent will exit after the command completes.
@@ -36,23 +36,23 @@ Note the `--` separator, which is used to separate `trezor-agent`'s arguments fr
Example: Example:
``` ```
(trezor|keepkey|ledger)-agent -e ed25519 bob@example.com -- rsync up/ bob@example.com:/home/bob (trezor|keepkey|ledger|onlykey)-agent -e ed25519 bob@example.com -- rsync up/ bob@example.com:/home/bob
``` ```
As a shortcut you can run As a shortcut you can run
``` ```
$ (trezor|keepkey|ledger)-agent identity@myhost -s $ (trezor|keepkey|ledger|onlykey)-agent identity@myhost -s
``` ```
to start a shell with the proper environment. to start a shell with the proper environment.
##### 3. Connect to a server directly via `(trezor|keepkey|ledger)-agent` ##### 3. Connect to a server directly via `(trezor|keepkey|ledger|onlykey)-agent`
If you just want to connect to a server this is the simplest way to do it: If you just want to connect to a server this is the simplest way to do it:
``` ```
$ (trezor|keepkey|ledger)-agent user@remotehost -c $ (trezor|keepkey|ledger|onlykey)-agent user@remotehost -c
``` ```
The identity `user@remotehost` is used as both the destination user and host as well as for key derivation, so you must generate a separate key for each host you connect to. The identity `user@remotehost` is used as both the destination user and host as well as for key derivation, so you must generate a separate key for each host you connect to.
@@ -118,7 +118,7 @@ The same works for Mercurial (e.g. on [BitBucket](https://confluence.atlassian.c
##### 1. Create these files in `~/.config/systemd/user` ##### 1. Create these files in `~/.config/systemd/user`
Replace `trezor` with `keepkey` or `ledger` as required. Replace `trezor` with `keepkey` or `ledger` or `onlykey` as required.
###### `trezor-ssh-agent.service` ###### `trezor-ssh-agent.service`
@@ -139,9 +139,9 @@ If you've installed `trezor-agent` locally you may have to change the path in `E
Replace `IDENTITY` with the identity you used when exporting the public key. Replace `IDENTITY` with the identity you used when exporting the public key.
`IDENTITY` can be a path (starting with `/`) to a file containing a list of public keys `IDENTITY` can be a path (starting with `/`) to a file containing a list of public keys
generated by Trezor. I.e. `/home/myUser/.ssh/trezor.conf` with one public key per line. generated by Trezor. I.e. `/home/myUser/.ssh/trezor.conf` with one public key per line.
This is a more convenient way to have a systemd setup that has to handle multiple This is a more convenient way to have a systemd setup that has to handle multiple
keys/hosts. keys/hosts.
When updating the file, make sure to restart trezor-agent. When updating the file, make sure to restart trezor-agent.
@@ -185,7 +185,7 @@ export SSH_AUTH_SOCK=$(systemctl show --user --property=Listen trezor-ssh-agent.
``` ```
Make sure the SSH_AUTH_SOCK variable matches the location of the socket that trezor-agent Make sure the SSH_AUTH_SOCK variable matches the location of the socket that trezor-agent
is listening on: `ps -x | grep trezor-agent`. In this setup trezor-agent should start is listening on: `ps -x | grep trezor-agent`. In this setup trezor-agent should start
automatically when the socket is opened. automatically when the socket is opened.
##### 4. SSH will now automatically use your device key in all terminals. ##### 4. SSH will now automatically use your device key in all terminals.

376
libagent/device/onlykey.py Normal file
View File

@@ -0,0 +1,376 @@
# pylint: disable=too-many-locals,too-many-statements,too-many-branches
# pylint: disable=attribute-defined-outside-init
"""OnlyKey-related code (see https://www.onlykey.io/)."""
import logging
import hashlib
import codecs
import time
import ecdsa
import nacl.signing
import unidecode
from . import interface
# import pgpy
# from pgpy import PGPKey
log = logging.getLogger(__name__)
class OnlyKey(interface.Device):
"""Connection to OnlyKey device."""
@classmethod
def package_name(cls):
"""Python package name (at PyPI)."""
return 'onlykey-agent'
@property
def _defs(self):
from . import onlykey_defs
return onlykey_defs
def connect(self):
"""Enumerate and connect to the first USB HID interface."""
try:
self.device_name = 'OnlyKey'
self.ok = self._defs.OnlyKey()
self.ok.set_time(time.time())
self.okversion = self.ok.read_string(timeout_ms=500)
self.okversion = self.okversion[8:]
self.skeyslot = 132
self.dkeyslot = 132
except Exception as e:
raise interface.NotFoundError('{} not connected: "{}"') from e
def set_skey(self, skey):
"""Set signing key to use."""
self.skeyslot = skey
log.debug('Setting skey slot = %s', skey)
def set_dkey(self, dkey):
"""Set decryption key to use."""
self.dkeyslot = dkey
log.debug('Setting dkey slot = %s', dkey)
def import_pub(self, pubkey):
"""Import PGP public key."""
self.import_pubkey = pubkey
log.debug('Public key to import = %s', pubkey)
# self.import_pubkey_obj, _ = pgpy.PGPKey.from_blob(pubkey)
# self.import_pubkey_bytes = bytes(self.import_pubkey_obj)
def get_sk_dk(self):
"""Get default signing key and decryption key slots."""
self.set_skey(132)
self.set_dkey(132)
def sig_hash(self, sighash):
"""Set signature hashing algorithm to use."""
if sighash in (b'rsa-sha2-512', b'rsa-sha2-256'):
self.sighash = sighash
log.info('Setting RSA signature Hash Type =%s', sighash)
def close(self):
"""Close connection."""
log.info('disconnected from %s', self.device_name)
self.ok.close()
def pubkey(self, identity, ecdh=False):
"""Return public key."""
curve_name = identity.get_curve_name(ecdh=ecdh)
if identity.identity_dict['proto'] != 'ssh' and hasattr('self', 'skeyslot') is False:
self.get_sk_dk()
if identity.identity_dict['proto'] != 'ssh' and self.dkeyslot < 132 and ecdh is True:
this_slot_id = self.dkeyslot
log.info('Key Slot =%s', this_slot_id)
elif self.skeyslot < 132 and ecdh is False:
this_slot_id = self.skeyslot
log.info('Key Slot =%s', this_slot_id)
else:
this_slot_id = 132
log.info('Requesting public key from key slot =%s', this_slot_id)
log.debug('"%s" getting public key (%s) from %s',
identity.to_string(), curve_name, self)
# Calculate hash for key derivation input data
if identity.identity_dict['proto'] == 'ssh':
if identity.identity_dict.get('user'):
id_parts = unidecode.unidecode(identity.identity_dict['user'] + '@' +
identity.identity_dict['host']).encode('ascii')
else:
id_parts = unidecode.unidecode(identity.identity_dict['host']).encode('ascii')
else:
id_parts = identity.to_bytes()
log.info('Identity to hash =%s', id_parts)
h1 = hashlib.sha256()
h1.update(id_parts)
data = h1.hexdigest()
log.info('Identity hash =%s', data)
if this_slot_id > 100:
if curve_name == 'curve25519':
data = '04' + data
elif curve_name == 'secp256k1':
# Not currently supported by agent, for future use
data = '03' + data
elif curve_name == 'nist256p1':
data = '02' + data
elif curve_name == 'ed25519':
data = '01' + data
else:
data = '00' + data
self.ok.send_message(msg=self._defs.Message.OKGETPUBKEY, slot_id=this_slot_id, payload=data)
log.info('curve name= %s', repr(curve_name))
t_end = time.time() + 1.5
if curve_name != 'rsa':
while time.time() < t_end:
try:
ok_pubkey = self.ok.read_bytes(timeout_ms=100)
if len(ok_pubkey) == 64 and len(set(ok_pubkey[0:63])) != 1:
break
except Exception as e:
raise interface.DeviceError(e)
log.info('received= %s', repr(ok_pubkey))
if len(set(ok_pubkey[34:63])) == 1:
if curve_name in ('nist256p1', 'secp256k1'):
raise interface.DeviceError("Public key curve does not match requested type")
ok_pubkey = bytearray(ok_pubkey[0:32])
log.info('Received Public Key generated by OnlyKey= %s', repr(ok_pubkey.hex()))
vk = nacl.signing.VerifyKey(bytes(ok_pubkey),
encoder=nacl.encoding.RawEncoder)
log.info('vk= %s', repr(vk))
# time.sleep(3)
return vk
elif len(ok_pubkey) == 64:
ok_pubkey = bytearray(ok_pubkey[0:64])
if curve_name in ('ed25519', 'curve25519'):
raise interface.DeviceError("Public key curve does not match requested type")
log.info('Received Public Key generated by OnlyKey= %s', repr(ok_pubkey))
if identity.curve_name == 'nist256p1':
vk = ecdsa.VerifyingKey.from_string(ok_pubkey, curve=ecdsa.NIST256p)
else:
vk = ecdsa.VerifyingKey.from_string(ok_pubkey, curve=ecdsa.SECP256k1)
return vk
else:
ok_pubkey = []
while time.time() < t_end:
try:
ok_pub_part = self.ok.read_bytes(timeout_ms=100)
if len(ok_pub_part) == 64 and len(set(ok_pub_part[0:63])) != 1:
log.info('received part= %s', repr(ok_pub_part))
ok_pubkey += ok_pub_part
# Todo know RSA type to know how many packets
except Exception as e:
raise interface.DeviceError(e)
log.info('received= %s', repr(ok_pubkey))
if len(ok_pubkey) == 256:
# https://security.stackexchange.com/questions/42268/how-do-i-get-the-rsa-bit-length-with-the-pubkey-and-openssl
ok_pubkey = b'\x00\x00\x00\x07' + b'\x73\x73\x68\x2d\x72\x73\x61' + \
b'\x00\x00\x00\x03' + b'\x01\x00\x01' + \
b'\x00\x00\x01\x01' + b'\x00' + bytes(ok_pubkey)
# ok_pubkey = b'\x00\x00\x00\x07' + b'\x72\x73\x61\x2d\x73\x68\x61\x32\x2d\x32\x35\x
# 36' + b'\x00\x00\x00\x03' + b'\x01\x00\x01' + b'\x00\x00\x01\x01' + b'\x00' + byte
# s(ok_pubkey)
elif len(ok_pubkey) == 512:
ok_pubkey = b'\x00\x00\x00\x07' + b'\x73\x73\x68\x2d\x72\x73\x61' + \
b'\x00\x00\x00\x03' + b'\x01\x00\x01' + \
b'\x00\x00\x02\x01' + b'\x00' + bytes(ok_pubkey)
else:
raise interface.DeviceError("Error response length is not a valid public key")
log.info('pubkey len = %s', len(ok_pubkey))
return ok_pubkey
def sign(self, identity, blob):
"""Sign given blob and return the signature (as bytes)."""
curve_name = identity.get_curve_name(ecdh=False)
log.debug('"%s" signing %r (%s) on %s',
identity.to_string(), blob, curve_name, self)
if identity.identity_dict['proto'] != 'ssh' and hasattr('self', 'skeyslot') is False:
self.get_sk_dk()
# Calculate hash for SSH signing
if curve_name == 'rsa':
if self.sighash == b'rsa-sha2-512':
log.info('rsa-sha2-512')
h1 = hashlib.sha512()
h1.update(blob)
data = h1.hexdigest()
data = codecs.decode(data, 'hex_codec')
elif self.sighash == b'rsa-sha2-256':
log.info('rsa-sha2-256')
h1 = hashlib.sha256()
h1.update(blob)
data = h1.hexdigest()
data = codecs.decode(data, 'hex_codec')
else:
# Calculate hash for key derivation input data
h1 = hashlib.sha256()
if identity.identity_dict['proto'] == 'ssh':
if identity.identity_dict.get('user'):
id_parts = unidecode.unidecode(identity.identity_dict['user'] + '@' +
identity.identity_dict['host']).encode('ascii')
else:
id_parts = unidecode.unidecode(identity.identity_dict['host']).encode('ascii')
else:
id_parts = identity.to_bytes()
h1.update(id_parts)
data = h1.hexdigest()
data = codecs.decode(data, 'hex_codec')
log.info('Identity to hash =%s', id_parts)
log.info('Identity hash =%s', data)
# Determine type of key to derive on OnlyKey for signature
# Slot 132 used for derived key, slots 101-116 used for stored ecc keys
# slots 1-4 used for stored RSA keys
if self.skeyslot == 132:
if curve_name == 'ed25519':
this_slot_id = 201
log.info('Key type ed25519')
elif curve_name == 'nist256p1':
this_slot_id = 202
log.info('Key type nistp256')
else:
this_slot_id = 203
log.info('Key type secp256k1')
# Send data and identity hash
raw_message = blob + data
else:
this_slot_id = self.skeyslot
# Send just data to sign
raw_message = blob
h2 = hashlib.sha256()
h2.update(raw_message)
d = h2.digest()
assert len(d) == 32
b1, b2, b3 = get_button(self, d[0]), get_button(self, d[15]), get_button(self, d[31])
log.info('Key Slot =%s', this_slot_id)
print('Enter the 3 digit challenge code on OnlyKey to authorize '+identity.to_string())
print('{} {} {}'.format(b1, b2, b3))
t_end = time.time() + 22
if curve_name != 'rsa':
self.ok.send_large_message2(msg=self._defs.Message.OKSIGN, payload=raw_message,
slot_id=this_slot_id)
while time.time() < t_end:
try:
result = self.ok.read_bytes(timeout_ms=100)
if len(result) == 64 and len(set(result[0:63])) != 1:
break
except Exception as e:
raise interface.DeviceError(e)
if len(result) >= 60:
log.info('received= %s', repr(result))
while len(result) < 64:
result.append(0)
log.info('disconnected from %s', self.device_name)
self.ok.close()
return bytes(result)
else:
self.ok.send_large_message2(msg=self._defs.Message.OKSIGN, payload=data,
slot_id=this_slot_id)
result = []
while time.time() < t_end:
try:
sig_part = self.ok.read_bytes(timeout_ms=100)
if len(sig_part) == 64 and len(set(sig_part[0:63])) != 1:
log.info('received part= %s', repr(sig_part))
result += sig_part
t_end = time.time() + 1
# Todo know RSA type to know how many packets
except Exception as e:
raise interface.DeviceError(e)
log.info('received= %s', repr(result))
return bytes(result)
raise Exception('failed to sign challenge')
def ecdh(self, identity, pubkey):
"""Get shared session key using Elliptic Curve Diffie-Hellman."""
curve_name = identity.get_curve_name(ecdh=True)
log.debug('"%s" shared session key (%s) for %r from %s',
identity.to_string(), curve_name, pubkey, self)
# Calculate hash for key derivation input data
h1 = hashlib.sha256()
if identity.identity_dict['proto'] == 'ssh':
if identity.identity_dict.get('user'):
id_parts = unidecode.unidecode(identity.identity_dict['user'] + '@' +
identity.identity_dict['host']).encode('ascii')
else:
id_parts = unidecode.unidecode(identity.identity_dict['host']).encode('ascii')
else:
id_parts = identity.to_bytes()
h1.update(id_parts)
log.info('Identity to hash =%s', id_parts)
data = h1.hexdigest()
log.info('Identity hash =%s', data)
data = codecs.decode(data, 'hex_codec')
# Determine type of key to derive on OnlyKey for ecdh
# Slot 132 used for derived key, slots 101-116 used for stored ecc keys,
# slots 1-4 used for stored RSA keys
if self.dkeyslot == 132:
if curve_name == 'curve25519':
this_slot_id = 204
log.info('Key type curve25519')
elif curve_name == 'nist256p1':
this_slot_id = 202
log.info('Key type nistp256')
else:
this_slot_id = 203
log.info('Key type secp256k1')
raw_message = pubkey + data
else:
this_slot_id = self.dkeyslot
raw_message = pubkey
log.info('Key Slot =%s', this_slot_id)
log.info('data hash =%s', data)
h2 = hashlib.sha256()
h2.update(raw_message)
d = h2.digest()
assert len(d) == 32
b1, b2, b3 = get_button(self, d[0]), get_button(self, d[15]), get_button(self, d[31])
self.ok.send_large_message2(msg=self._defs.Message.OKDECRYPT, payload=raw_message,
slot_id=this_slot_id)
print('Enter the 3 digit challenge code on OnlyKey to authorize ' + identity.to_string())
print('{} {} {}'.format(b1, b2, b3))
t_end = time.time() + 22
if curve_name != 'rsa':
while time.time() < t_end:
try:
result = self.ok.read_bytes(timeout_ms=100)
if len(result) == 64 and len(set(result[0:63])) != 1:
break
except Exception as e:
raise interface.DeviceError(e)
if len(set(result[34:63])) == 1:
result = b'\x04' + bytes(result[0:32])
else:
result = []
while time.time() < t_end:
try:
dec_part = self.ok.read_bytes(timeout_ms=100)
if len(dec_part) == 64 and len(set(dec_part[0:63])) != 1:
log.info('received part= %s', repr(dec_part))
result += dec_part
t_end = time.time() + 1
# Todo know RSA type to know how many packets
except Exception as e:
raise interface.DeviceError(e)
log.info('received= %s', repr(result))
log.info('disconnected from %s', self.device_name)
self.ok.close()
return bytes(result)
def get_button(self, byte):
"""Return button number."""
if str(self.okversion) == 'v0.2-beta.8c':
return byte % 5 + 1
else:
return byte % 6 + 1

View File

@@ -0,0 +1,5 @@
"""OnlyKey-related definitions."""
# pylint: disable=unused-import,import-error,no-name-in-module
from onlykey import OnlyKey, Message

View File

@@ -159,7 +159,7 @@ def run_process(command, environ):
try: try:
p = subprocess.Popen(args=command, env=env) p = subprocess.Popen(args=command, env=env)
except OSError as e: except OSError as e:
raise OSError('cannot run %r: %s' % (command, e)) raise OSError('cannot run %r: %s' % (command, e)) from e
log.debug('subprocess %d is running', p.pid) log.debug('subprocess %d is running', p.pid)
ret = p.wait() ret = p.wait()
log.debug('subprocess %d exited: %d', p.pid, ret) log.debug('subprocess %d exited: %d', p.pid, ret)

View File

@@ -149,9 +149,9 @@ class Handler:
try: try:
sig_bytes = key['verifier'](sig=signature, msg=blob) sig_bytes = key['verifier'](sig=signature, msg=blob)
log.info('signature status: OK') log.info('signature status: OK')
except formats.ecdsa.BadSignatureError: except formats.ecdsa.BadSignatureError as e:
log.exception('signature status: ERROR') log.exception('signature status: ERROR')
raise ValueError('invalid ECDSA signature') raise ValueError('invalid ECDSA signature') from e
log.debug('signature size: %d bytes', len(sig_bytes)) log.debug('signature size: %d bytes', len(sig_bytes))

View File

@@ -17,6 +17,10 @@ PUBKEY_TEXT = ('ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzd'
class MockDevice(device.interface.Device): # pylint: disable=abstract-method class MockDevice(device.interface.Device): # pylint: disable=abstract-method
@classmethod
def package_name(cls):
return 'fake-device-agent'
def connect(self): # pylint: disable=no-self-use def connect(self): # pylint: disable=no-self-use
return mock.Mock() return mock.Mock()

View File

@@ -3,7 +3,7 @@ from setuptools import setup
setup( setup(
name='libagent', name='libagent',
version='0.14.1', version='0.14.2',
description='Using hardware wallets as SSH/GPG agent', description='Using hardware wallets as SSH/GPG agent',
author='Roman Zeyde', author='Roman Zeyde',
author_email='roman.zeyde@gmail.com', author_email='roman.zeyde@gmail.com',