Add OnlyKey support
This commit is contained in:
@@ -14,7 +14,7 @@ See the following blog posts about this tool:
|
||||
- [TREZOR Firmware 1.4.0 — GPG 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)
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
* [`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
|
||||
* [`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`
|
||||
|
||||
7
agents/onlykey/onlykey_agent.py
Normal file
7
agents/onlykey/onlykey_agent.py
Normal 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
40
agents/onlykey/setup.py
Normal 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',
|
||||
]},
|
||||
)
|
||||
@@ -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
|
||||
```
|
||||
|
||||
# 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,
|
||||
see [this issue](https://github.com/romanz/trezor-agent/issues/28) for fixing it.
|
||||
|
||||
@@ -18,14 +18,14 @@ Thanks!
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
@@ -203,7 +203,7 @@ Follow [these instructions](enigmail.md) to set up Enigmail in Thunderbird.
|
||||
|
||||
##### 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`
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
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).
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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:
|
||||
|
||||
```
|
||||
(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.
|
||||
@@ -28,7 +28,7 @@ The identity (ex: `identity@myhost`) is used to derive the public key and is add
|
||||
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.
|
||||
@@ -36,23 +36,23 @@ Note the `--` separator, which is used to separate `trezor-agent`'s arguments fr
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
$ (trezor|keepkey|ledger)-agent identity@myhost -s
|
||||
$ (trezor|keepkey|ledger|onlykey)-agent identity@myhost -s
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
$ (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.
|
||||
@@ -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`
|
||||
|
||||
Replace `trezor` with `keepkey` or `ledger` as required.
|
||||
Replace `trezor` with `keepkey` or `ledger` or `onlykey` as required.
|
||||
|
||||
###### `trezor-ssh-agent.service`
|
||||
|
||||
|
||||
376
libagent/device/onlykey.py
Normal file
376
libagent/device/onlykey.py
Normal 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
|
||||
5
libagent/device/onlykey_defs.py
Normal file
5
libagent/device/onlykey_defs.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""OnlyKey-related definitions."""
|
||||
|
||||
# pylint: disable=unused-import,import-error,no-name-in-module
|
||||
|
||||
from onlykey import OnlyKey, Message
|
||||
@@ -159,7 +159,7 @@ def run_process(command, environ):
|
||||
try:
|
||||
p = subprocess.Popen(args=command, env=env)
|
||||
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)
|
||||
ret = p.wait()
|
||||
log.debug('subprocess %d exited: %d', p.pid, ret)
|
||||
|
||||
@@ -149,9 +149,9 @@ class Handler:
|
||||
try:
|
||||
sig_bytes = key['verifier'](sig=signature, msg=blob)
|
||||
log.info('signature status: OK')
|
||||
except formats.ecdsa.BadSignatureError:
|
||||
except formats.ecdsa.BadSignatureError as e:
|
||||
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))
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ PUBKEY_TEXT = ('ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzd'
|
||||
|
||||
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
|
||||
return mock.Mock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user