Compare commits
No commits in common. "master" and "0.1.6" have entirely different histories.
39
README.md
39
README.md
|
@ -1,44 +1,13 @@
|
||||||
# Python tools for Ledger Blue and Nano S
|
# Python tools for Ledger Blue and Nano S
|
||||||
|
|
||||||
This package contains Python tools to communicate with Ledger Blue and Nano S and manage applications life cycle.
|
This package contains Python tools to communicate with Ledger Blue and Nano S and manage applications life cycle
|
||||||
|
|
||||||
It is recommended to install this package in a [Virtual Environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/) in your native environment (not a Docker image) to avoid hidapi issues.
|
The life cycle management requires [libsecp256k1](https://github.com/ludbb/secp256k1-py) Python bindings compiled with ECDH support. It is recommended to install this package in a [Virtual Environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/) in your native environment (not a Docker image) through
|
||||||
|
|
||||||
```
|
```
|
||||||
virtualenv ledger
|
virtualenv ledger
|
||||||
source ledger/bin/activate
|
source ledger/bin/activate
|
||||||
pip install ledgerblue
|
SECP_BUNDLED_EXPERIMENTAL=1 pip install secp256k1
|
||||||
|
pip install git+https://github.com/LedgerHQ/blue-loader-python.git
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation pre-requisites
|
|
||||||
|
|
||||||
|
|
||||||
* libudev-dev
|
|
||||||
* libusb-1.0-0-dev
|
|
||||||
* python-dev (python 2.7)
|
|
||||||
* virtualenv
|
|
||||||
|
|
||||||
This package can optionally work with [libsecp256k1](https://github.com/ludbb/secp256k1-py) Python bindings compiled with ECDH support. If you wish to enable libsecp256k1 bindings, make sure to install libsecp256k1 as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
SECP_BUNDLED_EXPERIMENTAL=1 pip --no-cache-dir install --no-binary secp256k1 secp256k1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Giving permissions on udev
|
|
||||||
|
|
||||||
When running on Linux, make sure the following rules have been added to `/etc/udev/rules.d/`:
|
|
||||||
|
|
||||||
```
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000", MODE="0660", TAG+="uaccess", TAG+="udev-acl" OWNER="<UNIX username>"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001", MODE="0660", TAG+="uaccess", TAG+="udev-acl" OWNER="<UNIX username>"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Target ID
|
|
||||||
|
|
||||||
Use the following Target IDs (--targetId option) when running commands directly:
|
|
||||||
|
|
||||||
* 0x31100002 on Nano S (until firmware 1.3.1, included)
|
|
||||||
* 0x31100003 on Nano S (after firmware 1.3.1)
|
|
||||||
* 0x31000002 on Blue
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
# Versioning
|
|
||||||
|
|
||||||
The version for this repository is always the same as the version number for the
|
|
||||||
documentation. In `/doc/source/conf.py`, the `version` option will always be the
|
|
||||||
latest full version (latest version tag, not including pre-releases) and the
|
|
||||||
`release` option will be the same, except including pre-releases. As such, they
|
|
||||||
should always be bumped by the commit that was tagged with a version number.
|
|
||||||
|
|
||||||
The checklist for releasing a new version of this repository (and by extension,
|
|
||||||
of the documentation) is as follows:
|
|
||||||
|
|
||||||
1. Create a final commit that bumps the version number(s) in
|
|
||||||
`/doc/source/conf.py` and `/setup.py`.
|
|
||||||
2. Tag that commit with the appropriate version number.
|
|
||||||
3. Done! RTD should find the tag and build the docs automagically.
|
|
19
doc/Makefile
19
doc/Makefile
|
@ -1,19 +0,0 @@
|
||||||
# Minimal Makefile for Sphinx documentation
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = python -msphinx
|
|
||||||
SPHINXPROJ = BOLOSPythonLoader
|
|
||||||
SOURCEDIR = source
|
|
||||||
BUILDDIR = build
|
|
||||||
|
|
||||||
# Put it first so that "make" without argument is like "make help".
|
|
||||||
help:
|
|
||||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
||||||
|
|
||||||
.PHONY: help Makefile
|
|
||||||
|
|
||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
||||||
%: Makefile
|
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
|
@ -1,26 +0,0 @@
|
||||||
# BOLOS Python Tools Documentation
|
|
||||||
|
|
||||||
The latest version of this repository can be viewed, pre-built, here:
|
|
||||||
https://ledger.readthedocs.io/projects/blue-loader-python.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
If you wish, you may install Sphinx and build this documentation into a
|
|
||||||
collection of HTML files yourself.
|
|
||||||
|
|
||||||
Firstly, make sure you have [pip
|
|
||||||
installed](https://pip.pypa.io/en/stable/installing/).
|
|
||||||
|
|
||||||
Secondly, install Sphinx and the Read the Docs theme:
|
|
||||||
|
|
||||||
```
|
|
||||||
pip install sphinx sphinx_rtd_theme
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, build:
|
|
||||||
|
|
||||||
```
|
|
||||||
make html
|
|
||||||
```
|
|
||||||
|
|
||||||
You will need internet access for intersphinx to work properly.
|
|
|
@ -1 +0,0 @@
|
||||||
sphinx_argparse
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* override table width restrictions */
|
|
||||||
@media screen and (min-width: 767px) {
|
|
||||||
|
|
||||||
.wy-table-responsive table td {
|
|
||||||
/* !important prevents the common CSS stylesheets from overriding
|
|
||||||
this as on RTD they are loaded after this stylesheet */
|
|
||||||
white-space: normal !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wy-table-responsive {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# BOLOS Python Loader documentation build configuration file.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.abspath('../../'))
|
|
||||||
|
|
||||||
def setup(app):
|
|
||||||
app.add_stylesheet('theme_overrides.css') # Override wide tables in RTD theme
|
|
||||||
|
|
||||||
# General Configuration
|
|
||||||
# =====================
|
|
||||||
|
|
||||||
extensions = []
|
|
||||||
|
|
||||||
source_suffix = ['.rst']
|
|
||||||
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
project = u'BOLOS Python Loader'
|
|
||||||
copyright = u'2017, Ledger Team'
|
|
||||||
author = u'Ledger Team'
|
|
||||||
|
|
||||||
version = u'0.1.15'
|
|
||||||
release = u'0.1.15'
|
|
||||||
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# Options for HTML Output
|
|
||||||
# =======================
|
|
||||||
|
|
||||||
html_theme = 'sphinx_rtd_theme'
|
|
||||||
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# sphinxarg
|
|
||||||
# =========
|
|
||||||
|
|
||||||
extensions += ['sphinxarg.ext']
|
|
||||||
|
|
||||||
# intersphinx
|
|
||||||
# ===========
|
|
||||||
|
|
||||||
extensions += ['sphinx.ext.intersphinx']
|
|
||||||
|
|
||||||
intersphinx_mapping = {
|
|
||||||
'ledger': ('https://ledger.readthedocs.io/en/2/', None)
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
BOLOS Python Loader
|
|
||||||
===================
|
|
||||||
|
|
||||||
The BOLOS Python loader is a Python library and collection of scripts for
|
|
||||||
interfacing with and managing BOLOS devices from a host computer. See the
|
|
||||||
`Python loader GitHub repository
|
|
||||||
<https://github.com/LedgerHQ/blue-loader-python>`_ for download and installation
|
|
||||||
instructions.
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
scripts
|
|
||||||
script_reference
|
|
|
@ -1,212 +0,0 @@
|
||||||
Script Reference
|
|
||||||
================
|
|
||||||
|
|
||||||
.. _checkGenuine.py:
|
|
||||||
|
|
||||||
checkGenuine.py
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.checkGenuine
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.checkGenuine
|
|
||||||
|
|
||||||
.. _deleteApp.py:
|
|
||||||
|
|
||||||
deleteApp.py
|
|
||||||
------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.deleteApp
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.deleteApp
|
|
||||||
|
|
||||||
.. _derivePassphrase.py:
|
|
||||||
|
|
||||||
derivePassphrase.py
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.derivePassphrase
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.derivePassphrase
|
|
||||||
|
|
||||||
.. _endorsementSetupLedger.py:
|
|
||||||
|
|
||||||
endorsementSetupLedger.py
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.endorsementSetupLedger
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.endorsementSetupLedger
|
|
||||||
|
|
||||||
.. _endorsementSetup.py:
|
|
||||||
|
|
||||||
endorsementSetup.py
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.endorsementSetup
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.endorsementSetup
|
|
||||||
|
|
||||||
.. _genCAPair.py:
|
|
||||||
|
|
||||||
genCAPair.py
|
|
||||||
------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.genCAPair
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.genCAPair
|
|
||||||
|
|
||||||
.. _hashApp.py:
|
|
||||||
|
|
||||||
hashApp.py
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.hashApp
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.hashApp
|
|
||||||
|
|
||||||
.. _hostOnboard.py:
|
|
||||||
|
|
||||||
hostOnboard.py
|
|
||||||
--------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.hostOnboard
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.hostOnboard
|
|
||||||
|
|
||||||
.. _listApps.py:
|
|
||||||
|
|
||||||
listApps.py
|
|
||||||
-----------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.listApps
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.listApps
|
|
||||||
|
|
||||||
.. _loadApp.py:
|
|
||||||
|
|
||||||
loadApp.py
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.loadApp
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.loadApp
|
|
||||||
|
|
||||||
.. _loadMCU.py:
|
|
||||||
|
|
||||||
loadMCU.py
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.loadMCU
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.loadMCU
|
|
||||||
|
|
||||||
.. _mcuBootloader.py:
|
|
||||||
|
|
||||||
mcuBootloader.py
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.mcuBootloader
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.mcuBootloader
|
|
||||||
|
|
||||||
.. _resetCustomCA.py:
|
|
||||||
|
|
||||||
resetCustomCA.py
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.resetCustomCA
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.resetCustomCA
|
|
||||||
|
|
||||||
.. _runApp.py:
|
|
||||||
|
|
||||||
runApp.py
|
|
||||||
---------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.runApp
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.runApp
|
|
||||||
|
|
||||||
.. _runScript.py:
|
|
||||||
|
|
||||||
runScript.py
|
|
||||||
------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.runScript
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.runScript
|
|
||||||
|
|
||||||
.. _setupCustomCA.py:
|
|
||||||
|
|
||||||
setupCustomCA.py
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.setupCustomCA
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.setupCustomCA
|
|
||||||
|
|
||||||
.. _signApp.py:
|
|
||||||
|
|
||||||
signApp.py
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.signApp
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.signApp
|
|
||||||
|
|
||||||
.. _updateFirmware.py:
|
|
||||||
|
|
||||||
updateFirmware.py
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.updateFirmware
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.updateFirmware
|
|
||||||
|
|
||||||
.. _verifyApp.py:
|
|
||||||
|
|
||||||
verifyApp.py
|
|
||||||
------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.verifyApp
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.verifyApp
|
|
||||||
|
|
||||||
.. _verifyEndorsement1.py:
|
|
||||||
|
|
||||||
verifyEndorsement1.py
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.verifyEndorsement1
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.verifyEndorsement1
|
|
||||||
|
|
||||||
.. _verifyEndorsement2.py:
|
|
||||||
|
|
||||||
verifyEndorsement2.py
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. argparse::
|
|
||||||
:module: ledgerblue.verifyEndorsement2
|
|
||||||
:func: get_argparser
|
|
||||||
:prog: python -m ledgerblue.verifyEndorsement2
|
|
|
@ -1,22 +0,0 @@
|
||||||
Scripts
|
|
||||||
=======
|
|
||||||
|
|
||||||
The Python loader includes a collection of useful scripts for managing BOLOS
|
|
||||||
devices. This section includes an overview of some of the most important scripts
|
|
||||||
and how they can be used.
|
|
||||||
|
|
||||||
In order to use any of these scripts, the device must be in the dashboard
|
|
||||||
application (no apps are open, the device should display a list of installed
|
|
||||||
apps).
|
|
||||||
|
|
||||||
Here is an example using the :ref:`deleteApp.py` script from the command-line:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
python -m ledgerblue.deleteApp --targetId 0x31100002 --appName "Hello World"
|
|
||||||
|
|
||||||
The above command will delete the app named "Hello World" from the connected
|
|
||||||
Leger Nano S.
|
|
||||||
|
|
||||||
See the :doc:`script_reference` for the detailed documentation about each
|
|
||||||
script.
|
|
|
@ -57,7 +57,7 @@ class BLEDongle(Dongle):
|
||||||
|
|
||||||
def exchange(self, apdu, timeout=20000):
|
def exchange(self, apdu, timeout=20000):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("=> %s" % hexlify(apdu))
|
print "=> %s" % hexlify(apdu)
|
||||||
apdu = wrapCommandAPDU(0, apdu, DEFAULT_BLE_CHUNK, True)
|
apdu = wrapCommandAPDU(0, apdu, DEFAULT_BLE_CHUNK, True)
|
||||||
offset = 0
|
offset = 0
|
||||||
while(offset < len(apdu)):
|
while(offset < len(apdu)):
|
||||||
|
@ -78,8 +78,8 @@ class BLEDongle(Dongle):
|
||||||
sw = (result[swOffset] << 8) + result[swOffset + 1]
|
sw = (result[swOffset] << 8) + result[swOffset + 1]
|
||||||
response = result[dataStart : dataLength + dataStart]
|
response = result[dataStart : dataLength + dataStart]
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("<= %s%.2x" % (hexlify(response), sw))
|
print "<= %s%.2x" % (hexlify(response), sw)
|
||||||
if sw != 0x9000:
|
if sw <> 0x9000:
|
||||||
raise CommException("Invalid status %04x" % sw, sw)
|
raise CommException("Invalid status %04x" % sw, sw)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
||||||
# source: BlueHSMServer.proto
|
|
||||||
|
|
||||||
import sys
|
|
||||||
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
|
|
||||||
from google.protobuf import descriptor as _descriptor
|
|
||||||
from google.protobuf import message as _message
|
|
||||||
from google.protobuf import reflection as _reflection
|
|
||||||
from google.protobuf import symbol_database as _symbol_database
|
|
||||||
from google.protobuf import descriptor_pb2
|
|
||||||
# @@protoc_insertion_point(imports)
|
|
||||||
|
|
||||||
_sym_db = _symbol_database.Default()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
|
||||||
name='BlueHSMServer.proto',
|
|
||||||
package='bluehsmserver',
|
|
||||||
serialized_pb=_b('\n\x13\x42lueHSMServer.proto\x12\rbluehsmserver\"7\n\tParameter\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\x12\r\n\x05local\x18\x03 \x01(\x08\"\xa1\x01\n\x07Request\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nparameters\x18\x02 \x01(\x0c\x12\x11\n\treference\x18\x03 \x01(\t\x12\x0b\n\x03\x65lf\x18\x04 \x01(\x0c\x12\r\n\x05\x63lose\x18\x05 \x01(\x08\x12\x12\n\nlargeStack\x18\x06 \x01(\x08\x12\x33\n\x11remote_parameters\x18\x07 \x03(\x0b\x32\x18.bluehsmserver.Parameter\"L\n\x08Response\x12\n\n\x02id\x18\x01 \x02(\t\x12\x10\n\x08response\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x11\n\texception\x18\x04 \x01(\tB-\n\x1b\x63om.ledger.bluehsm.protobufB\x0c\x42lueHSMProtoH\x01')
|
|
||||||
)
|
|
||||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_PARAMETER = _descriptor.Descriptor(
|
|
||||||
name='Parameter',
|
|
||||||
full_name='bluehsmserver.Parameter',
|
|
||||||
filename=None,
|
|
||||||
file=DESCRIPTOR,
|
|
||||||
containing_type=None,
|
|
||||||
fields=[
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='name', full_name='bluehsmserver.Parameter.name', index=0,
|
|
||||||
number=1, type=9, cpp_type=9, label=2,
|
|
||||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='alias', full_name='bluehsmserver.Parameter.alias', index=1,
|
|
||||||
number=2, type=9, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='local', full_name='bluehsmserver.Parameter.local', index=2,
|
|
||||||
number=3, type=8, cpp_type=7, label=1,
|
|
||||||
has_default_value=False, default_value=False,
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
],
|
|
||||||
extensions=[
|
|
||||||
],
|
|
||||||
nested_types=[],
|
|
||||||
enum_types=[
|
|
||||||
],
|
|
||||||
options=None,
|
|
||||||
is_extendable=False,
|
|
||||||
extension_ranges=[],
|
|
||||||
oneofs=[
|
|
||||||
],
|
|
||||||
serialized_start=38,
|
|
||||||
serialized_end=93,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_REQUEST = _descriptor.Descriptor(
|
|
||||||
name='Request',
|
|
||||||
full_name='bluehsmserver.Request',
|
|
||||||
filename=None,
|
|
||||||
file=DESCRIPTOR,
|
|
||||||
containing_type=None,
|
|
||||||
fields=[
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='id', full_name='bluehsmserver.Request.id', index=0,
|
|
||||||
number=1, type=9, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='parameters', full_name='bluehsmserver.Request.parameters', index=1,
|
|
||||||
number=2, type=12, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b(""),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='reference', full_name='bluehsmserver.Request.reference', index=2,
|
|
||||||
number=3, type=9, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='elf', full_name='bluehsmserver.Request.elf', index=3,
|
|
||||||
number=4, type=12, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b(""),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='close', full_name='bluehsmserver.Request.close', index=4,
|
|
||||||
number=5, type=8, cpp_type=7, label=1,
|
|
||||||
has_default_value=False, default_value=False,
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='largeStack', full_name='bluehsmserver.Request.largeStack', index=5,
|
|
||||||
number=6, type=8, cpp_type=7, label=1,
|
|
||||||
has_default_value=False, default_value=False,
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='remote_parameters', full_name='bluehsmserver.Request.remote_parameters', index=6,
|
|
||||||
number=7, type=11, cpp_type=10, label=3,
|
|
||||||
has_default_value=False, default_value=[],
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
],
|
|
||||||
extensions=[
|
|
||||||
],
|
|
||||||
nested_types=[],
|
|
||||||
enum_types=[
|
|
||||||
],
|
|
||||||
options=None,
|
|
||||||
is_extendable=False,
|
|
||||||
extension_ranges=[],
|
|
||||||
oneofs=[
|
|
||||||
],
|
|
||||||
serialized_start=96,
|
|
||||||
serialized_end=257,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_RESPONSE = _descriptor.Descriptor(
|
|
||||||
name='Response',
|
|
||||||
full_name='bluehsmserver.Response',
|
|
||||||
filename=None,
|
|
||||||
file=DESCRIPTOR,
|
|
||||||
containing_type=None,
|
|
||||||
fields=[
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='id', full_name='bluehsmserver.Response.id', index=0,
|
|
||||||
number=1, type=9, cpp_type=9, label=2,
|
|
||||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='response', full_name='bluehsmserver.Response.response', index=1,
|
|
||||||
number=2, type=12, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b(""),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='message', full_name='bluehsmserver.Response.message', index=2,
|
|
||||||
number=3, type=9, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
_descriptor.FieldDescriptor(
|
|
||||||
name='exception', full_name='bluehsmserver.Response.exception', index=3,
|
|
||||||
number=4, type=9, cpp_type=9, label=1,
|
|
||||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
|
||||||
message_type=None, enum_type=None, containing_type=None,
|
|
||||||
is_extension=False, extension_scope=None,
|
|
||||||
options=None),
|
|
||||||
],
|
|
||||||
extensions=[
|
|
||||||
],
|
|
||||||
nested_types=[],
|
|
||||||
enum_types=[
|
|
||||||
],
|
|
||||||
options=None,
|
|
||||||
is_extendable=False,
|
|
||||||
extension_ranges=[],
|
|
||||||
oneofs=[
|
|
||||||
],
|
|
||||||
serialized_start=259,
|
|
||||||
serialized_end=335,
|
|
||||||
)
|
|
||||||
|
|
||||||
_REQUEST.fields_by_name['remote_parameters'].message_type = _PARAMETER
|
|
||||||
DESCRIPTOR.message_types_by_name['Parameter'] = _PARAMETER
|
|
||||||
DESCRIPTOR.message_types_by_name['Request'] = _REQUEST
|
|
||||||
DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE
|
|
||||||
|
|
||||||
Parameter = _reflection.GeneratedProtocolMessageType('Parameter', (_message.Message,), dict(
|
|
||||||
DESCRIPTOR = _PARAMETER,
|
|
||||||
__module__ = 'BlueHSMServer_pb2'
|
|
||||||
# @@protoc_insertion_point(class_scope:bluehsmserver.Parameter)
|
|
||||||
))
|
|
||||||
_sym_db.RegisterMessage(Parameter)
|
|
||||||
|
|
||||||
Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict(
|
|
||||||
DESCRIPTOR = _REQUEST,
|
|
||||||
__module__ = 'BlueHSMServer_pb2'
|
|
||||||
# @@protoc_insertion_point(class_scope:bluehsmserver.Request)
|
|
||||||
))
|
|
||||||
_sym_db.RegisterMessage(Request)
|
|
||||||
|
|
||||||
Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict(
|
|
||||||
DESCRIPTOR = _RESPONSE,
|
|
||||||
__module__ = 'BlueHSMServer_pb2'
|
|
||||||
# @@protoc_insertion_point(class_scope:bluehsmserver.Response)
|
|
||||||
))
|
|
||||||
_sym_db.RegisterMessage(Response)
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR.has_options = True
|
|
||||||
DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\033com.ledger.bluehsm.protobufB\014BlueHSMProtoH\001'))
|
|
||||||
# @@protoc_insertion_point(module_scope)
|
|
|
@ -1,55 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
from binascii import hexlify
|
|
||||||
import sys
|
|
||||||
|
|
||||||
TIMEOUT=20000
|
|
||||||
|
|
||||||
def hexstr(bstr):
|
|
||||||
if (sys.version_info.major == 3):
|
|
||||||
return hexlify(bstr).decode()
|
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
return hexlify(bstr)
|
|
||||||
return "<undecoded APDU<"
|
|
||||||
|
|
||||||
class DongleWait(object):
|
|
||||||
__metaclass__ = ABCMeta
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def waitFirstResponse(self, timeout):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Dongle(object):
|
|
||||||
__metaclass__ = ABCMeta
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def exchange(self, apdu, timeout=TIMEOUT):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def apduMaxDataSize(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setWaitImpl(self, waitImpl):
|
|
||||||
self.waitImpl = waitImpl
|
|
|
@ -1,168 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="""Use attestation to determine if the device is a genuine Ledger
|
|
||||||
device.""")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--issuerKey", help="Issuer key (hex encoded, default is batch 1)")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
def getDeployedSecretV2(dongle, masterPrivate, targetId, issuerKey):
|
|
||||||
testMaster = PrivateKey(bytes(masterPrivate))
|
|
||||||
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
|
||||||
targetid = bytearray(struct.pack('>I', targetId))
|
|
||||||
|
|
||||||
# identify
|
|
||||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# walk the chain
|
|
||||||
nonce = os.urandom(8)
|
|
||||||
apdu = bytearray([0xe0, 0x50, 0x00, 0x00]) + bytearray([len(nonce)]) + nonce
|
|
||||||
auth_info = dongle.exchange(apdu)
|
|
||||||
batch_signer_serial = auth_info[0:4]
|
|
||||||
deviceNonce = auth_info[4:12]
|
|
||||||
|
|
||||||
# if not found, get another pair
|
|
||||||
#if cardKey != testMasterPublic:
|
|
||||||
# raise Exception("Invalid batch public key")
|
|
||||||
|
|
||||||
dataToSign = bytes(bytearray([0x01]) + testMasterPublic)
|
|
||||||
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
|
||||||
signature = testMaster.ecdsa_serialize(signature)
|
|
||||||
certificate = bytearray([len(testMasterPublic)]) + testMasterPublic + bytearray([len(signature)]) + signature
|
|
||||||
apdu = bytearray([0xE0, 0x51, 0x00, 0x00]) + bytearray([len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# provide the ephemeral certificate
|
|
||||||
ephemeralPrivate = PrivateKey()
|
|
||||||
ephemeralPublic = bytearray(ephemeralPrivate.pubkey.serialize(compressed=False))
|
|
||||||
dataToSign = bytes(bytearray([0x11]) + nonce + deviceNonce + ephemeralPublic)
|
|
||||||
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
|
||||||
signature = testMaster.ecdsa_serialize(signature)
|
|
||||||
certificate = bytearray([len(ephemeralPublic)]) + ephemeralPublic + bytearray([len(signature)]) + signature
|
|
||||||
apdu = bytearray([0xE0, 0x51, 0x80, 0x00]) + bytearray([len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# walk the device certificates to retrieve the public key to use for authentication
|
|
||||||
index = 0
|
|
||||||
last_pub_key = PublicKey(binascii.unhexlify(issuerKey), raw=True)
|
|
||||||
devicePublicKey = None
|
|
||||||
while True:
|
|
||||||
if index == 0:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052000000')))
|
|
||||||
elif index == 1:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052800000')))
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
offset = 1
|
|
||||||
certificateHeader = certificate[offset : offset + certificate[offset-1]]
|
|
||||||
offset += certificate[offset-1] + 1
|
|
||||||
certificatePublicKey = certificate[offset : offset + certificate[offset-1]]
|
|
||||||
offset += certificate[offset-1] + 1
|
|
||||||
certificateSignatureArray = certificate[offset : offset + certificate[offset-1]]
|
|
||||||
certificateSignature = last_pub_key.ecdsa_deserialize(bytes(certificateSignatureArray))
|
|
||||||
# first cert contains a header field which holds the certificate's public key role
|
|
||||||
if index == 0:
|
|
||||||
devicePublicKey = certificatePublicKey
|
|
||||||
certificateSignedData = bytearray([0x02]) + certificateHeader + certificatePublicKey
|
|
||||||
# Could check if the device certificate is signed by the issuer public key
|
|
||||||
# ephemeral key certificate
|
|
||||||
else:
|
|
||||||
certificateSignedData = bytearray([0x12]) + deviceNonce + nonce + certificatePublicKey
|
|
||||||
if not last_pub_key.ecdsa_verify(bytes(certificateSignedData), certificateSignature):
|
|
||||||
return None
|
|
||||||
last_pub_key = PublicKey(bytes(certificatePublicKey), raw=True)
|
|
||||||
index = index + 1
|
|
||||||
|
|
||||||
# Commit device ECDH channel
|
|
||||||
dongle.exchange(bytearray.fromhex('E053000000'))
|
|
||||||
secret = last_pub_key.ecdh(binascii.unhexlify(ephemeralPrivate.serialize()))
|
|
||||||
if targetId&0xF == 0x2:
|
|
||||||
return secret[0:16]
|
|
||||||
elif targetId&0xF == 0x3:
|
|
||||||
ret = {}
|
|
||||||
ret['ecdh_secret'] = secret
|
|
||||||
ret['devicePublicKey'] = devicePublicKey
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey, PublicKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .commException import CommException
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
import struct
|
|
||||||
import os
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.targetId == None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
|
|
||||||
if args.issuerKey == None:
|
|
||||||
args.issuerKey = "0490f5c9d15a0134bb019d2afd0bf297149738459706e7ac5be4abc350a1f818057224fce12ec9a65de18ec34d6e8c24db927835ea1692b14c32e9836a75dad609"
|
|
||||||
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = str(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
|
|
||||||
genuine = False
|
|
||||||
ui = False
|
|
||||||
customCA = False
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
version = None
|
|
||||||
|
|
||||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId, args.issuerKey)
|
|
||||||
if secret != None:
|
|
||||||
try:
|
|
||||||
loader = HexLoader(dongle, 0xe0, True, secret)
|
|
||||||
version = loader.getVersion()
|
|
||||||
genuine = True
|
|
||||||
apps = loader.listApp()
|
|
||||||
while len(apps) != 0:
|
|
||||||
for app in apps:
|
|
||||||
if (app['flags'] & 0x08):
|
|
||||||
ui = True
|
|
||||||
if (app['flags'] & 0x400):
|
|
||||||
customCA = True
|
|
||||||
apps = loader.listApp(False)
|
|
||||||
except:
|
|
||||||
genuine = False
|
|
||||||
if genuine:
|
|
||||||
if ui:
|
|
||||||
print ("WARNING : Product is genuine but has a UI application loaded")
|
|
||||||
if customCA:
|
|
||||||
print ("WARNING : Product is genuine but has a Custom CA loaded")
|
|
||||||
if not ui and not customCA:
|
|
||||||
print ("Product is genuine")
|
|
||||||
print ("SE Version " + version['osVersion'])
|
|
||||||
print ("MCU Version " + version['mcuVersion'])
|
|
||||||
if 'mcuHash' in version:
|
|
||||||
print ("MCU Hash " + binascii.hexlify(version['mcuHash']).decode('ascii'))
|
|
||||||
else:
|
|
||||||
print ("Product is NOT genuine")
|
|
|
@ -20,38 +20,38 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from .commException import CommException
|
from .commException import CommException
|
||||||
from .ledgerWrapper import wrapCommandAPDU, unwrapResponseAPDU
|
from .ledgerWrapper import wrapCommandAPDU, unwrapResponseAPDU
|
||||||
from .Dongle import *
|
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from .commU2F import getDongle as getDongleU2F
|
|
||||||
from .commHTTP import getDongle as getDongleHTTP
|
|
||||||
import hid
|
import hid
|
||||||
|
import time
|
||||||
|
|
||||||
APDUGEN=None
|
try:
|
||||||
if "APDUGEN" in os.environ and len(os.environ["APDUGEN"]) != 0:
|
from smartcard.Exceptions import NoCardException
|
||||||
APDUGEN=os.environ["APDUGEN"]
|
from smartcard.System import readers
|
||||||
# Force use of U2F if required
|
from smartcard.util import toHexString, toBytes
|
||||||
U2FKEY=None
|
SCARD = True
|
||||||
if "U2FKEY" in os.environ and len(os.environ["U2FKEY"]) != 0:
|
except ImportError:
|
||||||
U2FKEY=os.environ["U2FKEY"]
|
SCARD = False
|
||||||
# Force use of MCUPROXY if required
|
|
||||||
MCUPROXY=None
|
class DongleWait(object):
|
||||||
if "MCUPROXY" in os.environ and len(os.environ["MCUPROXY"]) != 0:
|
__metaclass__ = ABCMeta
|
||||||
MCUPROXY=os.environ["MCUPROXY"]
|
|
||||||
|
@abstractmethod
|
||||||
# Force use of MCUPROXY if required
|
def waitFirstResponse(self, timeout):
|
||||||
PCSC=None
|
pass
|
||||||
if "PCSC" in os.environ and len(os.environ["PCSC"]) != 0:
|
|
||||||
PCSC=os.environ["PCSC"]
|
class Dongle(object):
|
||||||
if PCSC:
|
__metaclass__ = ABCMeta
|
||||||
try:
|
|
||||||
from smartcard.Exceptions import NoCardException
|
@abstractmethod
|
||||||
from smartcard.System import readers
|
def exchange(self, apdu, timeout=20000):
|
||||||
from smartcard.util import toHexString, toBytes
|
pass
|
||||||
except ImportError:
|
|
||||||
PCSC = False
|
@abstractmethod
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setWaitImpl(self, waitImpl):
|
||||||
|
self.waitImpl = waitImpl
|
||||||
|
|
||||||
class HIDDongleHIDAPI(Dongle, DongleWait):
|
class HIDDongleHIDAPI(Dongle, DongleWait):
|
||||||
|
|
||||||
|
@ -62,25 +62,20 @@ class HIDDongleHIDAPI(Dongle, DongleWait):
|
||||||
self.waitImpl = self
|
self.waitImpl = self
|
||||||
self.opened = True
|
self.opened = True
|
||||||
|
|
||||||
def exchange(self, apdu, timeout=TIMEOUT):
|
def exchange(self, apdu, timeout=20000):
|
||||||
if APDUGEN:
|
|
||||||
print("%s" % hexstr(apdu))
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("HID => %s" % hexstr(apdu))
|
print "=> %s" % hexlify(apdu)
|
||||||
if self.ledger:
|
if self.ledger:
|
||||||
apdu = wrapCommandAPDU(0x0101, apdu, 64)
|
apdu = wrapCommandAPDU(0x0101, apdu, 64)
|
||||||
padSize = len(apdu) % 64
|
padSize = len(apdu) % 64
|
||||||
tmp = apdu
|
tmp = apdu
|
||||||
if padSize != 0:
|
if padSize <> 0:
|
||||||
tmp.extend([0] * (64 - padSize))
|
tmp.extend([0] * (64 - padSize))
|
||||||
offset = 0
|
offset = 0
|
||||||
while(offset != len(tmp)):
|
while(offset <> len(tmp)):
|
||||||
data = tmp[offset:offset + 64]
|
data = tmp[offset:offset + 64]
|
||||||
data = bytearray([0]) + data
|
data = bytearray([0]) + data
|
||||||
if self.device.write(data) < 0:
|
self.device.write(data)
|
||||||
raise BaseException("Error while writing")
|
|
||||||
offset += 64
|
offset += 64
|
||||||
dataLength = 0
|
dataLength = 0
|
||||||
dataStart = 2
|
dataStart = 2
|
||||||
|
@ -119,14 +114,9 @@ class HIDDongleHIDAPI(Dongle, DongleWait):
|
||||||
sw = (result[swOffset] << 8) + result[swOffset + 1]
|
sw = (result[swOffset] << 8) + result[swOffset + 1]
|
||||||
response = result[dataStart : dataLength + dataStart]
|
response = result[dataStart : dataLength + dataStart]
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("HID <= %s%.2x" % (hexstr(response), sw))
|
print "<= %s%.2x" % (hexlify(response), sw)
|
||||||
if sw != 0x9000:
|
if sw <> 0x9000:
|
||||||
possibleCause = "Unknown reason"
|
raise CommException("Invalid status %04x" % sw, sw)
|
||||||
if sw == 0x6982:
|
|
||||||
possibleCause = "Have you uninstalled the existing CA with resetCustomCA first?"
|
|
||||||
if sw == 0x6484:
|
|
||||||
possibleCause = "Are you using the correct targetId?"
|
|
||||||
raise CommException("Invalid status %04x (%s)" % (sw, possibleCause), sw, response)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def waitFirstResponse(self, timeout):
|
def waitFirstResponse(self, timeout):
|
||||||
|
@ -137,12 +127,9 @@ class HIDDongleHIDAPI(Dongle, DongleWait):
|
||||||
if not len(data):
|
if not len(data):
|
||||||
if time.time() - start > timeout:
|
if time.time() - start > timeout:
|
||||||
raise CommException("Timeout")
|
raise CommException("Timeout")
|
||||||
time.sleep(0.0001)
|
time.sleep(0.02)
|
||||||
return bytearray(data)
|
return bytearray(data)
|
||||||
|
|
||||||
def apduMaxDataSize(self):
|
|
||||||
return 255
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self.opened:
|
if self.opened:
|
||||||
try:
|
try:
|
||||||
|
@ -159,15 +146,15 @@ class DongleSmartcard(Dongle):
|
||||||
self.waitImpl = self
|
self.waitImpl = self
|
||||||
self.opened = True
|
self.opened = True
|
||||||
|
|
||||||
def exchange(self, apdu, timeout=TIMEOUT):
|
def exchange(self, apdu, timeout=20000):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("SC => %s" % hexstr(apdu))
|
print "=> %s" % hexlify(apdu)
|
||||||
response, sw1, sw2 = self.device.transmit(toBytes(hexlify(apdu)))
|
response, sw1, sw2 = self.device.transmit(toBytes(hexlify(apdu)))
|
||||||
sw = (sw1 << 8) | sw2
|
sw = (sw1 << 8) | sw2
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("SC <= %s%.2x" % (hexstr(response).replace(" ", ""), sw))
|
print "<= %s%.2x" % (toHexString(response).replace(" ", ""), sw)
|
||||||
if sw != 0x9000:
|
if sw <> 0x9000:
|
||||||
raise CommException("Invalid status %04x" % sw, sw, bytearray(response))
|
raise CommException("Invalid status %04x" % sw, sw)
|
||||||
return bytearray(response)
|
return bytearray(response)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
@ -179,32 +166,24 @@ class DongleSmartcard(Dongle):
|
||||||
self.opened = False
|
self.opened = False
|
||||||
|
|
||||||
def getDongle(debug=False, selectCommand=None):
|
def getDongle(debug=False, selectCommand=None):
|
||||||
if APDUGEN:
|
|
||||||
return HIDDongleHIDAPI(None, True, debug)
|
|
||||||
|
|
||||||
if not U2FKEY is None:
|
|
||||||
return getDongleU2F(scrambleKey=U2FKEY, debug=debug)
|
|
||||||
if MCUPROXY is not None:
|
|
||||||
return getDongleHTTP(remote_host=MCUPROXY, debug=debug)
|
|
||||||
dev = None
|
dev = None
|
||||||
hidDevicePath = None
|
hidDevicePath = None
|
||||||
ledger = True
|
ledger = True
|
||||||
for hidDevice in hid.enumerate(0, 0):
|
for hidDevice in hid.enumerate(0, 0):
|
||||||
if hidDevice['vendor_id'] == 0x2c97:
|
if hidDevice['vendor_id'] == 0x2c97:
|
||||||
if ('interface_number' in hidDevice and hidDevice['interface_number'] == 0) or ('usage_page' in hidDevice and hidDevice['usage_page'] == 0xffa0):
|
hidDevicePath = hidDevice['path']
|
||||||
hidDevicePath = hidDevice['path']
|
|
||||||
if hidDevicePath is not None:
|
if hidDevicePath is not None:
|
||||||
dev = hid.device()
|
dev = hid.device()
|
||||||
dev.open_path(hidDevicePath)
|
dev.open_path(hidDevicePath)
|
||||||
dev.set_nonblocking(True)
|
dev.set_nonblocking(True)
|
||||||
return HIDDongleHIDAPI(dev, ledger, debug)
|
return HIDDongleHIDAPI(dev, ledger, debug)
|
||||||
if PCSC:
|
if SCARD:
|
||||||
connection = None
|
connection = None
|
||||||
for reader in readers():
|
for reader in readers():
|
||||||
try:
|
try:
|
||||||
connection = reader.createConnection()
|
connection = reader.createConnection()
|
||||||
connection.connect()
|
connection.connect()
|
||||||
if selectCommand != None:
|
if selectCommand <> None:
|
||||||
response, sw1, sw2 = connection.transmit(toBytes("00A4040010FF4C4547522E57414C5430312E493031"))
|
response, sw1, sw2 = connection.transmit(toBytes("00A4040010FF4C4547522E57414C5430312E493031"))
|
||||||
sw = (sw1 << 8) | sw2
|
sw = (sw1 << 8) | sw2
|
||||||
if sw == 0x9000:
|
if sw == 0x9000:
|
||||||
|
|
|
@ -19,10 +19,9 @@
|
||||||
|
|
||||||
class CommException(Exception):
|
class CommException(Exception):
|
||||||
|
|
||||||
def __init__(self, message, sw=0x6f00, data=None):
|
def __init__(self, message, sw=0x6f00):
|
||||||
self.message = message
|
self.message = message
|
||||||
self.sw = sw
|
self.sw = sw
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
buf = "Exception : " + self.message
|
buf = "Exception : " + self.message
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
from .commException import CommException
|
|
||||||
from .ledgerWrapper import wrapCommandAPDU, unwrapResponseAPDU
|
|
||||||
from binascii import hexlify
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
def hexstr(bstr):
|
|
||||||
if (sys.version_info.major == 3):
|
|
||||||
return hexlify(bstr).decode()
|
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
return hexlify(bstr)
|
|
||||||
return "<undecoded APDU<"
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPProxy(object):
|
|
||||||
|
|
||||||
def __init__(self, remote_host="localhost:8081", debug=False):
|
|
||||||
self.remote_host = "http://" + remote_host
|
|
||||||
self.debug = debug
|
|
||||||
|
|
||||||
|
|
||||||
def exchange(self, apdu):
|
|
||||||
if self.debug:
|
|
||||||
print("=> %s" % hexstr(apdu))
|
|
||||||
|
|
||||||
try:
|
|
||||||
ret = requests.post(self.remote_host + "/send_apdu", params={"data": hexstr(apdu)})
|
|
||||||
|
|
||||||
while True:
|
|
||||||
ret = requests.post(self.remote_host + "/fetch_apdu")
|
|
||||||
if ret.text != "no response apdu yet":
|
|
||||||
print("<= %s" % ret.text)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
|
|
||||||
return bytearray(str(ret.text).decode("hex"))
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def exchange_seph_event(self, event):
|
|
||||||
if self.debug >= 3:
|
|
||||||
print("=> %s" % hexstr(event))
|
|
||||||
|
|
||||||
try:
|
|
||||||
ret = requests.post(self.remote_host + "/send_seph_event", params={"data": event.encode("hex")})
|
|
||||||
return ret.text
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
def poll_status(self):
|
|
||||||
if self.debug >= 5:
|
|
||||||
print("=> Waiting for a status")
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
ret = requests.post(self.remote_host + "/fetch_status")
|
|
||||||
if ret.text != "no status yet":
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
time.sleep(0.05)
|
|
||||||
|
|
||||||
return bytearray(str(ret.text).decode("hex"))
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
if self.debug:
|
|
||||||
print("=> Reset")
|
|
||||||
|
|
||||||
try:
|
|
||||||
ret = requests.post(self.remote_host + "/reset")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getDongle(remote_host="localhost", debug=False):
|
|
||||||
|
|
||||||
return HTTPProxy(remote_host, debug)
|
|
|
@ -1,333 +0,0 @@
|
||||||
# Copyright (c) 2013 Yubico AB
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or
|
|
||||||
# without modification, are permitted provided that the following
|
|
||||||
# conditions are met:
|
|
||||||
#
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above
|
|
||||||
# copyright notice, this list of conditions and the following
|
|
||||||
# disclaimer in the documentation and/or other materials provided
|
|
||||||
# with the distribution.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
||||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
||||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
||||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import traceback
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
from .ledgerWrapper import wrapCommandAPDU, unwrapResponseAPDU
|
|
||||||
from binascii import hexlify
|
|
||||||
from .Dongle import *
|
|
||||||
|
|
||||||
import binascii
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import hid
|
|
||||||
from u2flib_host.device import U2FDevice
|
|
||||||
from u2flib_host.yubicommon.compat import byte2int, int2byte
|
|
||||||
from u2flib_host.constants import INS_ENROLL, INS_SIGN
|
|
||||||
from u2flib_host import u2f, exc
|
|
||||||
from u2flib_host.utils import websafe_decode, websafe_encode
|
|
||||||
from hashlib import sha256
|
|
||||||
|
|
||||||
from .commException import CommException
|
|
||||||
|
|
||||||
TIMEOUT=30000
|
|
||||||
|
|
||||||
DEVICES = [
|
|
||||||
(0x1050, 0x0200), # Gnubby
|
|
||||||
(0x1050, 0x0113), # YubiKey NEO U2F
|
|
||||||
(0x1050, 0x0114), # YubiKey NEO OTP+U2F
|
|
||||||
(0x1050, 0x0115), # YubiKey NEO U2F+CCID
|
|
||||||
(0x1050, 0x0116), # YubiKey NEO OTP+U2F+CCID
|
|
||||||
(0x1050, 0x0120), # Security Key by Yubico
|
|
||||||
(0x1050, 0x0410), # YubiKey Plus
|
|
||||||
(0x1050, 0x0402), # YubiKey 4 U2F
|
|
||||||
(0x1050, 0x0403), # YubiKey 4 OTP+U2F
|
|
||||||
(0x1050, 0x0406), # YubiKey 4 U2F+CCID
|
|
||||||
(0x1050, 0x0407), # YubiKey 4 OTP+U2F+CCID
|
|
||||||
(0x2581, 0xf1d0), # Plug-Up U2F Security Key
|
|
||||||
(0x2581, 0xf1d1), # Ledger Production U2F Dongle
|
|
||||||
(0x2c97, 0x0000), # Ledger Blue
|
|
||||||
(0x2c97, 0x0001), # Ledger Nano S
|
|
||||||
(0x2c97, 0x0002), # Ledger Aramis
|
|
||||||
(0x2c97, 0x0003), # Ledger HW2
|
|
||||||
(0x2c97, 0x0004), # Ledger Blend
|
|
||||||
(0x2c97, 0xf1d0), # Plug-Up U2F Security Key
|
|
||||||
]
|
|
||||||
HID_RPT_SIZE = 64
|
|
||||||
|
|
||||||
TYPE_INIT = 0x80
|
|
||||||
U2F_VENDOR_FIRST = 0x40
|
|
||||||
|
|
||||||
CMD_INIT = 0x06
|
|
||||||
CMD_WINK = 0x08
|
|
||||||
CMD_APDU = 0x03
|
|
||||||
U2FHID_YUBIKEY_DEVICE_CONFIG = U2F_VENDOR_FIRST
|
|
||||||
|
|
||||||
STAT_ERR = 0xbf
|
|
||||||
|
|
||||||
def _read_timeout(dev, size, timeout=TIMEOUT):
|
|
||||||
if (timeout > 0):
|
|
||||||
timeout += time.time()
|
|
||||||
while timeout == 0 or time.time() < timeout:
|
|
||||||
resp = dev.read(size)
|
|
||||||
if resp:
|
|
||||||
return resp
|
|
||||||
time.sleep(0.01)
|
|
||||||
return []
|
|
||||||
|
|
||||||
class U2FHIDError(Exception):
|
|
||||||
def __init__(self, code):
|
|
||||||
super(Exception, self).__init__("U2FHIDError: 0x%02x" % code)
|
|
||||||
self.code = code
|
|
||||||
|
|
||||||
|
|
||||||
class HIDDevice(U2FDevice):
|
|
||||||
|
|
||||||
"""
|
|
||||||
U2FDevice implementation using the HID transport.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, path):
|
|
||||||
self.path = path
|
|
||||||
self.cid = b"\xff\xff\xff\xff"
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
self.handle = hid.device()
|
|
||||||
self.handle.open_path(self.path)
|
|
||||||
self.handle.set_nonblocking(True)
|
|
||||||
self.init()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if hasattr(self, 'handle'):
|
|
||||||
self.handle.close()
|
|
||||||
del self.handle
|
|
||||||
|
|
||||||
def init(self):
|
|
||||||
nonce = os.urandom(8)
|
|
||||||
resp = self.call(CMD_INIT, nonce)
|
|
||||||
while resp[:8] != nonce:
|
|
||||||
print("Wrong nonce, read again...")
|
|
||||||
resp = self._read_resp(self.cid, CMD_INIT)
|
|
||||||
self.cid = resp[8:12]
|
|
||||||
|
|
||||||
def set_mode(self, mode):
|
|
||||||
data = mode + b"\x0f\x00\x00"
|
|
||||||
self.call(U2FHID_YUBIKEY_DEVICE_CONFIG, data)
|
|
||||||
|
|
||||||
def _do_send_apdu(self, apdu_data):
|
|
||||||
return self.call(CMD_APDU, apdu_data)
|
|
||||||
|
|
||||||
def wink(self):
|
|
||||||
self.call(CMD_WINK)
|
|
||||||
|
|
||||||
def _send_req(self, cid, cmd, data):
|
|
||||||
size = len(data)
|
|
||||||
bc_l = int2byte(size & 0xff)
|
|
||||||
bc_h = int2byte(size >> 8 & 0xff)
|
|
||||||
payload = cid + int2byte(TYPE_INIT | cmd) + bc_h + bc_l + \
|
|
||||||
data[:HID_RPT_SIZE - 7]
|
|
||||||
payload += b'\0' * (HID_RPT_SIZE - len(payload))
|
|
||||||
if self.handle.write([0] + [byte2int(c) for c in payload]) < 0:
|
|
||||||
raise exc.DeviceError("Cannot write to device!")
|
|
||||||
data = data[HID_RPT_SIZE - 7:]
|
|
||||||
seq = 0
|
|
||||||
while len(data) > 0:
|
|
||||||
payload = cid + int2byte(0x7f & seq) + data[:HID_RPT_SIZE - 5]
|
|
||||||
payload += b'\0' * (HID_RPT_SIZE - len(payload))
|
|
||||||
if self.handle.write([0] + [byte2int(c) for c in payload]) < 0:
|
|
||||||
raise exc.DeviceError("Cannot write to device!")
|
|
||||||
data = data[HID_RPT_SIZE - 5:]
|
|
||||||
seq += 1
|
|
||||||
|
|
||||||
def _read_resp(self, cid, cmd):
|
|
||||||
resp = b'.'
|
|
||||||
header = cid + int2byte(TYPE_INIT | cmd)
|
|
||||||
while resp and resp[:5] != header:
|
|
||||||
# allow for timeout
|
|
||||||
resp_vals = _read_timeout(self.handle, HID_RPT_SIZE)
|
|
||||||
resp = b''.join(int2byte(v) for v in resp_vals)
|
|
||||||
if resp[:5] == cid + int2byte(STAT_ERR):
|
|
||||||
raise U2FHIDError(byte2int(resp[7]))
|
|
||||||
|
|
||||||
if not resp:
|
|
||||||
raise exc.DeviceError("Invalid response from device!")
|
|
||||||
|
|
||||||
data_len = (byte2int(resp[5]) << 8) + byte2int(resp[6])
|
|
||||||
data = resp[7:min(7 + data_len, HID_RPT_SIZE)]
|
|
||||||
data_len -= len(data)
|
|
||||||
|
|
||||||
seq = 0
|
|
||||||
while data_len > 0:
|
|
||||||
resp_vals = _read_timeout(self.handle, HID_RPT_SIZE)
|
|
||||||
resp = b''.join(int2byte(v) for v in resp_vals)
|
|
||||||
if resp[:4] != cid:
|
|
||||||
raise exc.DeviceError("Wrong CID from device!")
|
|
||||||
if byte2int(resp[4:5]) != seq & 0x7f:
|
|
||||||
raise exc.DeviceError("Wrong SEQ from device!")
|
|
||||||
seq += 1
|
|
||||||
new_data = resp[5:min(5 + data_len, HID_RPT_SIZE)]
|
|
||||||
data_len -= len(new_data)
|
|
||||||
data += new_data
|
|
||||||
return data
|
|
||||||
|
|
||||||
def call(self, cmd, data=b''):
|
|
||||||
if isinstance(data, int):
|
|
||||||
data = int2byte(data)
|
|
||||||
|
|
||||||
self._send_req(self.cid, cmd, data)
|
|
||||||
return self._read_resp(self.cid, cmd)
|
|
||||||
|
|
||||||
class U2FTunnelDongle(Dongle, DongleWait):
|
|
||||||
|
|
||||||
def __init__(self, device, scrambleKey="", ledger=False, debug=False):
|
|
||||||
self.device = device
|
|
||||||
self.scrambleKey = scrambleKey
|
|
||||||
self.ledger = ledger
|
|
||||||
self.debug = debug
|
|
||||||
self.waitImpl = self
|
|
||||||
self.opened = True
|
|
||||||
self.device.open()
|
|
||||||
|
|
||||||
def exchange(self, apdu, timeout=TIMEOUT):
|
|
||||||
if self.debug:
|
|
||||||
print("U2F => %s" % hexstr(apdu))
|
|
||||||
|
|
||||||
if (len(apdu)>=256):
|
|
||||||
raise CommException("Too long APDU to transport")
|
|
||||||
|
|
||||||
# wrap apdu
|
|
||||||
i=0
|
|
||||||
keyHandle = ""
|
|
||||||
while i < len(apdu):
|
|
||||||
val = apdu[i:i+1]
|
|
||||||
if len(self.scrambleKey) > 0:
|
|
||||||
val = chr(ord(val) ^ ord(self.scrambleKey[i % len(self.scrambleKey)]))
|
|
||||||
keyHandle += val
|
|
||||||
i+=1
|
|
||||||
|
|
||||||
client_param = sha256("u2f_tunnel".encode('utf8')).digest()
|
|
||||||
app_param = sha256("u2f_tunnel".encode('utf8')).digest()
|
|
||||||
|
|
||||||
request = client_param + app_param + int2byte(len(keyHandle)) + keyHandle
|
|
||||||
|
|
||||||
#p1 = 0x07 if check_only else 0x03
|
|
||||||
p1 = 0x03
|
|
||||||
p2 = 0
|
|
||||||
response = self.device.send_apdu(INS_SIGN, p1, p2, request)
|
|
||||||
|
|
||||||
if self.debug:
|
|
||||||
print("U2F <= %s%.2x" % (hexstr(response), 0x9000))
|
|
||||||
|
|
||||||
# check replied status words of the command (within the APDU tunnel)
|
|
||||||
if hexstr(response[-2:]) != "9000":
|
|
||||||
raise CommException("Invalid status words received: " + hexstr(response[-2:]));
|
|
||||||
|
|
||||||
# api expect a byte array, remove the appended status words
|
|
||||||
return bytearray(response[:-2])
|
|
||||||
|
|
||||||
def apduMaxDataSize(self):
|
|
||||||
return 256-5
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.device.close()
|
|
||||||
|
|
||||||
def waitFirstResponse(self, timeout):
|
|
||||||
raise CommException("Invalid use")
|
|
||||||
|
|
||||||
def getDongles(dev_class=None, scrambleKey="", debug=False):
|
|
||||||
dev_class = dev_class or HIDDevice
|
|
||||||
devices = []
|
|
||||||
for d in hid.enumerate(0, 0):
|
|
||||||
usage_page = d['usage_page']
|
|
||||||
if usage_page == 0xf1d0 and d['usage'] == 1:
|
|
||||||
devices.append(U2FTunnelDongle(dev_class(d['path']),scrambleKey, debug=debug))
|
|
||||||
# Usage page doesn't work on Linux
|
|
||||||
# well known devices
|
|
||||||
elif (d['vendor_id'], d['product_id']) in DEVICES:
|
|
||||||
device = HIDDevice(d['path'])
|
|
||||||
try:
|
|
||||||
device.open()
|
|
||||||
device.close()
|
|
||||||
devices.append(U2FTunnelDongle(dev_class(d['path']),scrambleKey, debug=debug))
|
|
||||||
except (exc.DeviceError, IOError, OSError):
|
|
||||||
pass
|
|
||||||
# unknown devices
|
|
||||||
else:
|
|
||||||
device = HIDDevice(d['path'])
|
|
||||||
try:
|
|
||||||
device.open()
|
|
||||||
# try a ping command to ensure a FIDO device, else timeout (BEST here, modulate the timeout, 2 seconds is way too big)
|
|
||||||
device.ping()
|
|
||||||
device.close()
|
|
||||||
devices.append(U2FTunnelDongle(dev_class(d['path']),scrambleKey, debug=debug))
|
|
||||||
except (exc.DeviceError, IOError, OSError):
|
|
||||||
pass
|
|
||||||
return devices
|
|
||||||
|
|
||||||
def getDongle(path=None, dev_class=None, scrambleKey="", debug=False):
|
|
||||||
# if path is none, then use the first device
|
|
||||||
dev_class = dev_class or HIDDevice
|
|
||||||
devices = []
|
|
||||||
for d in hid.enumerate(0, 0):
|
|
||||||
if path is None or d['path'] == path:
|
|
||||||
usage_page = d['usage_page']
|
|
||||||
if usage_page == 0xf1d0 and d['usage'] == 1:
|
|
||||||
return U2FTunnelDongle(dev_class(d['path']),scrambleKey, debug=debug)
|
|
||||||
# Usage page doesn't work on Linux
|
|
||||||
# well known devices
|
|
||||||
elif (d['vendor_id'], d['product_id']) in DEVICES and ('interface_number' not in d or d['interface_number'] == 1):
|
|
||||||
#print d
|
|
||||||
device = HIDDevice(d['path'])
|
|
||||||
try:
|
|
||||||
device.open()
|
|
||||||
device.close()
|
|
||||||
return U2FTunnelDongle(dev_class(d['path']),scrambleKey, debug=debug)
|
|
||||||
except (exc.DeviceError, IOError, OSError):
|
|
||||||
traceback.print_exc()
|
|
||||||
pass
|
|
||||||
# unknown devices
|
|
||||||
# else:
|
|
||||||
# device = HIDDevice(d['path'])
|
|
||||||
# try:
|
|
||||||
# device.open()
|
|
||||||
# # try a ping command to ensure a FIDO device, else timeout (BEST here, modulate the timeout, 2 seconds is way too big)
|
|
||||||
# device.ping()
|
|
||||||
# device.close()
|
|
||||||
# return U2FTunnelDongle(dev_class(d['path']),scrambleKey, debug=debug)
|
|
||||||
# except (exc.DeviceError, IOError, OSError):
|
|
||||||
# traceback.print_exc()
|
|
||||||
# pass
|
|
||||||
raise CommException("No dongle found")
|
|
|
@ -17,67 +17,39 @@
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from secp256k1 import PrivateKey
|
||||||
|
from .comm import getDongle
|
||||||
|
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
||||||
|
from .hexLoader import HexLoader
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Delete the app with the specified name.")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--appName", help="The name of the application to delete")
|
|
||||||
parser.add_argument("--appHash", help="Set the application hash")
|
|
||||||
parser.add_argument("--rootPrivateKey", help="A private key used to establish a Secure Channel (hex encoded)")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--deployLegacy", help="Use legacy deployment API", action='store_true')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
def auto_int(x):
|
||||||
return int(x, 0)
|
return int(x, 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
parser = argparse.ArgumentParser()
|
||||||
from .ecWrapper import PrivateKey
|
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||||
from .comm import getDongle
|
parser.add_argument("--appName", help="Set the application name")
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
parser.add_argument("--rootPrivateKey", help="Set the root private key")
|
||||||
from .hexLoader import HexLoader
|
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
||||||
import binascii
|
parser.add_argument("--deployLegacy", help="Use legacy deployment API", action='store_true')
|
||||||
import sys
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.appName == None and args.appHash == None:
|
if args.appName == None:
|
||||||
raise Exception("Missing appName or appHash")
|
raise Exception("Missing appName")
|
||||||
if args.appName != None and args.appHash != None:
|
if args.targetId == None:
|
||||||
raise Exception("Set either appName or appHash")
|
args.targetId = 0x31000002
|
||||||
|
if args.rootPrivateKey == None:
|
||||||
|
privateKey = PrivateKey()
|
||||||
|
publicKey = str(privateKey.pubkey.serialize(compressed=False)).encode('hex')
|
||||||
|
print "Generated random root public key : " + publicKey
|
||||||
|
args.rootPrivateKey = privateKey.serialize().encode('ascii')
|
||||||
|
|
||||||
if args.appName != None:
|
dongle = getDongle(args.apdu)
|
||||||
if (sys.version_info.major == 3):
|
|
||||||
args.appName = bytes(args.appName,'ascii')
|
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
args.appName = bytes(args.appName)
|
|
||||||
|
|
||||||
if args.appHash != None:
|
if args.deployLegacy:
|
||||||
if (sys.version_info.major == 3):
|
secret = getDeployedSecretV1(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
||||||
args.appHash = bytes(args.appHash,'ascii')
|
else:
|
||||||
if (sys.version_info.major == 2):
|
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
||||||
args.appHash = bytes(args.appHash)
|
loader = HexLoader(dongle, 0xe0, True, secret)
|
||||||
args.appHash = bytearray.fromhex(args.appHash)
|
loader.deleteApp(args.appName)
|
||||||
|
|
||||||
|
|
||||||
if args.targetId == None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
if args.rootPrivateKey == None:
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
if args.deployLegacy:
|
|
||||||
secret = getDeployedSecretV1(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
else:
|
|
||||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
loader = HexLoader(dongle, 0xe0, True, secret)
|
|
||||||
|
|
||||||
if args.appName != None:
|
|
||||||
loader.deleteApp(args.appName)
|
|
||||||
if args.appHash != None:
|
|
||||||
loader.deleteAppByHash(args.appHash)
|
|
||||||
|
|
|
@ -17,22 +17,18 @@
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .ecWrapper import PrivateKey, PublicKey
|
from secp256k1 import PrivateKey, PublicKey
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
from .hexParser import IntelHexParser
|
from .hexParser import IntelHexParser
|
||||||
from .hexLoader import HexLoader
|
from .hexLoader import HexLoader
|
||||||
import binascii
|
|
||||||
|
|
||||||
def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
||||||
testMaster = PrivateKey(bytes(masterPrivate))
|
testMaster = PrivateKey(bytes(masterPrivate))
|
||||||
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
||||||
targetid = bytearray(struct.pack('>I', targetid))
|
targetid = bytearray(struct.pack('>I', targetid))
|
||||||
|
|
||||||
if targetId&0xF != 0x1:
|
|
||||||
raise BaseException("Target ID does not support SCP V1")
|
|
||||||
|
|
||||||
# identify
|
# identify
|
||||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
||||||
dongle.exchange(apdu)
|
dongle.exchange(apdu)
|
||||||
|
@ -42,13 +38,13 @@ def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
||||||
cardKey = batch_info[5:5 + batch_info[4]]
|
cardKey = batch_info[5:5 + batch_info[4]]
|
||||||
|
|
||||||
# if not found, get another pair
|
# if not found, get another pair
|
||||||
#if cardKey != testMasterPublic:
|
#if cardKey <> testMasterPublic:
|
||||||
# raise Exception("Invalid batch public key")
|
# raise Exception("Invalid batch public key")
|
||||||
|
|
||||||
# provide the ephemeral certificate
|
# provide the ephemeral certificate
|
||||||
ephemeralPrivate = PrivateKey()
|
ephemeralPrivate = PrivateKey()
|
||||||
ephemeralPublic = bytearray(ephemeralPrivate.pubkey.serialize(compressed=False))
|
ephemeralPublic = bytearray(ephemeralPrivate.pubkey.serialize(compressed=False))
|
||||||
print("Using ephemeral key %s" %binascii.hexlify(ephemeralPublic))
|
print "Using ephemeral key " + str(ephemeralPublic).encode('hex')
|
||||||
signature = testMaster.ecdsa_sign(bytes(ephemeralPublic))
|
signature = testMaster.ecdsa_sign(bytes(ephemeralPublic))
|
||||||
signature = testMaster.ecdsa_serialize(signature)
|
signature = testMaster.ecdsa_serialize(signature)
|
||||||
certificate = bytearray([len(ephemeralPublic)]) + ephemeralPublic + bytearray([len(signature)]) + signature
|
certificate = bytearray([len(ephemeralPublic)]) + ephemeralPublic + bytearray([len(signature)]) + signature
|
||||||
|
@ -67,7 +63,7 @@ def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
||||||
if not last_pub_key.ecdsa_verify(bytes(certificatePublic), certificateSignature):
|
if not last_pub_key.ecdsa_verify(bytes(certificatePublic), certificateSignature):
|
||||||
if index == 0:
|
if index == 0:
|
||||||
# Not an error if loading from user key
|
# Not an error if loading from user key
|
||||||
print("Broken certificate chain - loading from user key")
|
print "Broken certificate chain - loading from user key"
|
||||||
else:
|
else:
|
||||||
raise Exception("Broken certificate chain")
|
raise Exception("Broken certificate chain")
|
||||||
last_pub_key = PublicKey(bytes(certificatePublic), raw=True)
|
last_pub_key = PublicKey(bytes(certificatePublic), raw=True)
|
||||||
|
@ -76,15 +72,12 @@ def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
||||||
# Commit device ECDH channel
|
# Commit device ECDH channel
|
||||||
dongle.exchange(bytearray.fromhex('E053000000'))
|
dongle.exchange(bytearray.fromhex('E053000000'))
|
||||||
secret = last_pub_key.ecdh(bytes(ephemeralPrivate.serialize().decode('hex')))
|
secret = last_pub_key.ecdh(bytes(ephemeralPrivate.serialize().decode('hex')))
|
||||||
return secret[0:16]
|
return str(secret[0:16])
|
||||||
|
|
||||||
def getDeployedSecretV2(dongle, masterPrivate, targetId, signerCertChain=None, ecdh_secret_format=None):
|
def getDeployedSecretV2(dongle, masterPrivate, targetid):
|
||||||
testMaster = PrivateKey(bytes(masterPrivate))
|
testMaster = PrivateKey(bytes(masterPrivate))
|
||||||
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
||||||
targetid = bytearray(struct.pack('>I', targetId))
|
targetid = bytearray(struct.pack('>I', targetid))
|
||||||
|
|
||||||
if targetId&0xF == 0x1:
|
|
||||||
raise BaseException("Target ID does not support SCP V2")
|
|
||||||
|
|
||||||
# identify
|
# identify
|
||||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
||||||
|
@ -98,26 +91,21 @@ def getDeployedSecretV2(dongle, masterPrivate, targetId, signerCertChain=None, e
|
||||||
deviceNonce = auth_info[4:12]
|
deviceNonce = auth_info[4:12]
|
||||||
|
|
||||||
# if not found, get another pair
|
# if not found, get another pair
|
||||||
#if cardKey != testMasterPublic:
|
#if cardKey <> testMasterPublic:
|
||||||
# raise Exception("Invalid batch public key")
|
# raise Exception("Invalid batch public key")
|
||||||
|
|
||||||
if (signerCertChain):
|
print "Using test master key " + str(testMasterPublic).encode('hex')
|
||||||
for cert in signerCertChain:
|
dataToSign = bytes(bytearray([0x01]) + testMasterPublic)
|
||||||
apdu = bytearray([0xE0, 0x51, 0x00, 0x00]) + bytearray([len(cert)]) + cert
|
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
||||||
dongle.exchange(apdu)
|
signature = testMaster.ecdsa_serialize(signature)
|
||||||
else:
|
certificate = bytearray([len(testMasterPublic)]) + testMasterPublic + bytearray([len(signature)]) + signature
|
||||||
print("Using test master key %s " % binascii.hexlify(testMasterPublic))
|
apdu = bytearray([0xE0, 0x51, 0x00, 0x00]) + bytearray([len(certificate)]) + certificate
|
||||||
dataToSign = bytes(bytearray([0x01]) + testMasterPublic)
|
dongle.exchange(apdu)
|
||||||
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
|
||||||
signature = testMaster.ecdsa_serialize(signature)
|
|
||||||
certificate = bytearray([len(testMasterPublic)]) + testMasterPublic + bytearray([len(signature)]) + signature
|
|
||||||
apdu = bytearray([0xE0, 0x51, 0x00, 0x00]) + bytearray([len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# provide the ephemeral certificate
|
# provide the ephemeral certificate
|
||||||
ephemeralPrivate = PrivateKey()
|
ephemeralPrivate = PrivateKey()
|
||||||
ephemeralPublic = bytearray(ephemeralPrivate.pubkey.serialize(compressed=False))
|
ephemeralPublic = bytearray(ephemeralPrivate.pubkey.serialize(compressed=False))
|
||||||
print("Using ephemeral key %s" %binascii.hexlify(ephemeralPublic))
|
print "Using ephemeral key " + str(ephemeralPublic).encode('hex')
|
||||||
dataToSign = bytes(bytearray([0x11]) + nonce + deviceNonce + ephemeralPublic)
|
dataToSign = bytes(bytearray([0x11]) + nonce + deviceNonce + ephemeralPublic)
|
||||||
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
||||||
signature = testMaster.ecdsa_serialize(signature)
|
signature = testMaster.ecdsa_serialize(signature)
|
||||||
|
@ -127,50 +115,40 @@ def getDeployedSecretV2(dongle, masterPrivate, targetId, signerCertChain=None, e
|
||||||
|
|
||||||
# walk the device certificates to retrieve the public key to use for authentication
|
# walk the device certificates to retrieve the public key to use for authentication
|
||||||
index = 0
|
index = 0
|
||||||
last_dev_pub_key = PublicKey(bytes(testMasterPublic), raw=True)
|
last_pub_key = PublicKey(bytes(testMasterPublic), raw=True)
|
||||||
devicePublicKey = None
|
|
||||||
while True:
|
while True:
|
||||||
if index == 0:
|
if index == 0:
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052000000')))
|
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052000000')))
|
||||||
elif index == 1:
|
elif index == 1:
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052800000')))
|
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052800000')))
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
if len(certificate) == 0:
|
if len(certificate) == 0:
|
||||||
break
|
break
|
||||||
offset = 1
|
offset = 1
|
||||||
certificateHeader = certificate[offset : offset + certificate[offset-1]]
|
certificateHeader = certificate[offset : offset + certificate[offset-1]]
|
||||||
offset += certificate[offset-1] + 1
|
offset += certificate[offset-1] + 1
|
||||||
certificatePublicKey = certificate[offset : offset + certificate[offset-1]]
|
certificatePublicKey = certificate[offset : offset + certificate[offset-1]]
|
||||||
offset += certificate[offset-1] + 1
|
offset += certificate[offset-1] + 1
|
||||||
certificateSignatureArray = certificate[offset : offset + certificate[offset-1]]
|
certificateSignatureArray = certificate[offset : offset + certificate[offset-1]]
|
||||||
certificateSignature = last_dev_pub_key.ecdsa_deserialize(bytes(certificateSignatureArray))
|
certificateSignature = last_pub_key.ecdsa_deserialize(bytes(certificateSignatureArray))
|
||||||
# first cert contains a header field which holds the certificate's public key role
|
# first cert contains a header field which holds the certificate's public key role
|
||||||
if index == 0:
|
if index == 0:
|
||||||
devicePublicKey = certificatePublicKey
|
|
||||||
certificateSignedData = bytearray([0x02]) + certificateHeader + certificatePublicKey
|
certificateSignedData = bytearray([0x02]) + certificateHeader + certificatePublicKey
|
||||||
# Could check if the device certificate is signed by the issuer public key
|
# Could check if the device certificate is signed by the issuer public key
|
||||||
# ephemeral key certificate
|
# ephemeral key certificate
|
||||||
else:
|
else:
|
||||||
certificateSignedData = bytearray([0x12]) + deviceNonce + nonce + certificatePublicKey
|
certificateSignedData = bytearray([0x12]) + deviceNonce + nonce + certificatePublicKey
|
||||||
if not last_dev_pub_key.ecdsa_verify(bytes(certificateSignedData), certificateSignature):
|
if not last_pub_key.ecdsa_verify(bytes(certificateSignedData), certificateSignature):
|
||||||
if index == 0:
|
if index == 0:
|
||||||
# Not an error if loading from user key
|
# Not an error if loading from user key
|
||||||
print("Broken certificate chain - loading from user key")
|
print "Broken certificate chain - loading from user key"
|
||||||
else:
|
else:
|
||||||
raise Exception("Broken certificate chain")
|
raise Exception("Broken certificate chain")
|
||||||
last_dev_pub_key = PublicKey(bytes(certificatePublicKey), raw=True)
|
last_pub_key = PublicKey(bytes(certificatePublicKey), raw=True)
|
||||||
index = index + 1
|
index = index + 1
|
||||||
|
|
||||||
# Commit device ECDH channel
|
# Commit device ECDH channel
|
||||||
dongle.exchange(bytearray.fromhex('E053000000'))
|
dongle.exchange(bytearray.fromhex('E053000000'))
|
||||||
secret = last_dev_pub_key.ecdh(binascii.unhexlify(ephemeralPrivate.serialize()))
|
secret = last_pub_key.ecdh(bytes(ephemeralPrivate.serialize().decode('hex')))
|
||||||
|
return str(secret[0:16])
|
||||||
#forced to specific version
|
|
||||||
if ecdh_secret_format==1 or targetId&0xF == 0x2:
|
|
||||||
return secret[0:16]
|
|
||||||
elif targetId&0xF == 0x3:
|
|
||||||
ret = {}
|
|
||||||
ret['ecdh_secret'] = secret
|
|
||||||
ret['devicePublicKey'] = devicePublicKey
|
|
||||||
return ret
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Set a BIP 39 passphrase on the device.")
|
|
||||||
parser.add_argument("--persistent", help="""Persist passphrase as secondary PIN (otherwise, it's set as a temporary
|
|
||||||
passphrase)""", action='store_true')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .comm import getDongle
|
|
||||||
import getpass
|
|
||||||
import unicodedata
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
dongle = getDongle(False)
|
|
||||||
|
|
||||||
passphrase = getpass.getpass("Enter BIP39 passphrase : ")
|
|
||||||
if isinstance(passphrase, bytes):
|
|
||||||
passphrase = passphrase.decode(sys.stdin.encoding)
|
|
||||||
if len(passphrase) != 0:
|
|
||||||
if args.persistent:
|
|
||||||
p1 = 0x02
|
|
||||||
else:
|
|
||||||
p1 = 0x01
|
|
||||||
passphrase = unicodedata.normalize('NFKD', passphrase)
|
|
||||||
apdu = bytearray([0xE0, 0xD0, p1, 0x00, len(passphrase)]) + bytearray(passphrase, 'utf8')
|
|
||||||
dongle.exchange(apdu, timeout=300)
|
|
|
@ -1,144 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
try:
|
|
||||||
import secp256k1
|
|
||||||
USE_SECP = secp256k1.HAS_ECDH
|
|
||||||
except ImportError:
|
|
||||||
USE_SECP = False
|
|
||||||
|
|
||||||
if not USE_SECP:
|
|
||||||
import ecpy
|
|
||||||
from builtins import int
|
|
||||||
from ecpy.curves import Curve, Point
|
|
||||||
from ecpy.keys import ECPublicKey, ECPrivateKey
|
|
||||||
from ecpy.ecdsa import ECDSA
|
|
||||||
CURVE_SECP256K1 = Curve.get_curve('secp256k1')
|
|
||||||
SIGNER = ECDSA()
|
|
||||||
|
|
||||||
class PublicKey(object):
|
|
||||||
def __init__(self, pubkey=None, raw=False, flags=None, ctx=None):
|
|
||||||
if USE_SECP:
|
|
||||||
if flags == None:
|
|
||||||
flags = secp256k1.FLAG_VERIFY
|
|
||||||
self.obj = secp256k1.PublicKey(pubkey, raw, flags, ctx)
|
|
||||||
else:
|
|
||||||
if not raw:
|
|
||||||
raise Exception("Non raw init unsupported")
|
|
||||||
pubkey = pubkey[1:]
|
|
||||||
x = int.from_bytes(pubkey[0:32], 'big')
|
|
||||||
y = int.from_bytes(pubkey[32:], 'big')
|
|
||||||
self.obj = ECPublicKey(Point(x, y, CURVE_SECP256K1))
|
|
||||||
|
|
||||||
def ecdsa_deserialize(self, ser_sig):
|
|
||||||
if USE_SECP:
|
|
||||||
return self.obj.ecdsa_deserialize(ser_sig)
|
|
||||||
else:
|
|
||||||
return ser_sig
|
|
||||||
|
|
||||||
def serialize(self, compressed=True):
|
|
||||||
if USE_SECP:
|
|
||||||
return self.obj.serialize(compressed)
|
|
||||||
else:
|
|
||||||
if not compressed:
|
|
||||||
out = b"\x04"
|
|
||||||
out += self.obj.W.x.to_bytes(32, 'big')
|
|
||||||
out += self.obj.W.y.to_bytes(32, 'big')
|
|
||||||
else:
|
|
||||||
out = b"\x03" if ((self.obj.W.y & 1) != 0) else "\x02"
|
|
||||||
out += self.obj.W.x.to_bytes(32, 'big')
|
|
||||||
return out
|
|
||||||
|
|
||||||
def ecdh(self, scalar):
|
|
||||||
if USE_SECP:
|
|
||||||
return self.obj.ecdh(scalar)
|
|
||||||
else:
|
|
||||||
scalar = int.from_bytes(scalar, 'big')
|
|
||||||
point = self.obj.W * scalar
|
|
||||||
# libsecp256k1 style secret
|
|
||||||
out = b"\x03" if ((point.y & 1) != 0) else b"\x02"
|
|
||||||
out += point.x.to_bytes(32, 'big')
|
|
||||||
hash = hashlib.sha256()
|
|
||||||
hash.update(out)
|
|
||||||
return hash.digest()
|
|
||||||
|
|
||||||
def tweak_add(self, scalar):
|
|
||||||
if USE_SECP:
|
|
||||||
self.obj = self.obj.tweak_add(scalar)
|
|
||||||
else:
|
|
||||||
scalar = int.from_bytes(scalar, 'big')
|
|
||||||
privKey = ECPrivateKey(scalar, CURVE_SECP256K1)
|
|
||||||
self.obj = ECPublicKey(self.obj.W + privKey.get_public_key().W)
|
|
||||||
|
|
||||||
def ecdsa_verify(self, msg, raw_sig, raw=False, digest=hashlib.sha256):
|
|
||||||
if USE_SECP:
|
|
||||||
return self.obj.ecdsa_verify(msg, raw_sig, raw, digest)
|
|
||||||
else:
|
|
||||||
if not raw:
|
|
||||||
h = digest()
|
|
||||||
h.update(msg)
|
|
||||||
msg = h.digest()
|
|
||||||
raw_sig = bytearray(raw_sig)
|
|
||||||
return SIGNER.verify(msg, raw_sig, self.obj)
|
|
||||||
|
|
||||||
class PrivateKey(object):
|
|
||||||
|
|
||||||
def __init__(self, privkey=None, raw=True, flags=None, ctx=None):
|
|
||||||
if USE_SECP:
|
|
||||||
if flags == None:
|
|
||||||
flags = secp256k1.ALL_FLAGS
|
|
||||||
self.obj = secp256k1.PrivateKey(privkey, raw, flags, ctx)
|
|
||||||
self.pubkey = self.obj.pubkey
|
|
||||||
else:
|
|
||||||
if not raw:
|
|
||||||
raise Exception("Non raw init unsupported")
|
|
||||||
if privkey == None:
|
|
||||||
privkey = ecpy.ecrand.rnd(CURVE_SECP256K1.order)
|
|
||||||
else:
|
|
||||||
privkey = int.from_bytes(privkey,'big')
|
|
||||||
self.obj = ECPrivateKey(privkey, CURVE_SECP256K1)
|
|
||||||
pubkey = self.obj.get_public_key().W
|
|
||||||
out = b"\x04"
|
|
||||||
out += pubkey.x.to_bytes(32, 'big')
|
|
||||||
out += pubkey.y.to_bytes(32, 'big')
|
|
||||||
self.pubkey = PublicKey(out, raw=True)
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
if USE_SECP:
|
|
||||||
return self.obj.serialize()
|
|
||||||
else:
|
|
||||||
return "%.64x"%self.obj.d
|
|
||||||
|
|
||||||
def ecdsa_serialize(self, raw_sig):
|
|
||||||
if USE_SECP:
|
|
||||||
return self.obj.ecdsa_serialize(raw_sig)
|
|
||||||
else:
|
|
||||||
return raw_sig
|
|
||||||
|
|
||||||
def ecdsa_sign(self, msg, raw=False, digest=hashlib.sha256):
|
|
||||||
if USE_SECP:
|
|
||||||
return self.obj.ecdsa_sign(msg, raw, digest)
|
|
||||||
else:
|
|
||||||
if not raw:
|
|
||||||
h = digest()
|
|
||||||
h.update(msg)
|
|
||||||
msg = h.digest()
|
|
||||||
signature = SIGNER.sign(msg, self.obj)
|
|
||||||
return bytearray(signature)
|
|
|
@ -1,168 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="""Generate an attestation keypair, using the provided Owner private
|
|
||||||
key to sign the Owner Certificate.""")
|
|
||||||
parser.add_argument("--key", help="Which endorsement scheme to use (1 or 2)", type=auto_int)
|
|
||||||
parser.add_argument("--certificate", help="""Optional certificate to store if finalizing the endorsement (hex
|
|
||||||
encoded), if no private key is specified""")
|
|
||||||
parser.add_argument("--privateKey", help="""Optional private key to use to create a test certificate (hex encoded),
|
|
||||||
if no certificate is specified""")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--issuerKey", help="Issuer key (hex encoded, default is batch 1)")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
def hexstr(bstr):
|
|
||||||
if (sys.version_info.major == 3):
|
|
||||||
return binascii.hexlify(bstr).decode()
|
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
return binascii.hexlify(bstr)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def getDeployedSecretV2(dongle, masterPrivate, targetid, issuerKey):
|
|
||||||
testMaster = PrivateKey(bytes(masterPrivate))
|
|
||||||
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
|
||||||
targetid = bytearray(struct.pack('>I', targetid))
|
|
||||||
|
|
||||||
# identify
|
|
||||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# walk the chain
|
|
||||||
nonce = os.urandom(8)
|
|
||||||
apdu = bytearray([0xe0, 0x50, 0x00, 0x00]) + bytearray([len(nonce)]) + nonce
|
|
||||||
auth_info = dongle.exchange(apdu)
|
|
||||||
batch_signer_serial = auth_info[0:4]
|
|
||||||
deviceNonce = auth_info[4:12]
|
|
||||||
|
|
||||||
# if not found, get another pair
|
|
||||||
#if cardKey != testMasterPublic:
|
|
||||||
# raise Exception("Invalid batch public key")
|
|
||||||
|
|
||||||
dataToSign = bytes(bytearray([0x01]) + testMasterPublic)
|
|
||||||
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
|
||||||
signature = testMaster.ecdsa_serialize(signature)
|
|
||||||
certificate = bytearray([len(testMasterPublic)]) + testMasterPublic + bytearray([len(signature)]) + signature
|
|
||||||
apdu = bytearray([0xE0, 0x51, 0x00, 0x00]) + bytearray([len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# provide the ephemeral certificate
|
|
||||||
ephemeralPrivate = PrivateKey()
|
|
||||||
ephemeralPublic = bytearray(ephemeralPrivate.pubkey.serialize(compressed=False))
|
|
||||||
dataToSign = bytes(bytearray([0x11]) + nonce + deviceNonce + ephemeralPublic)
|
|
||||||
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
|
||||||
signature = testMaster.ecdsa_serialize(signature)
|
|
||||||
certificate = bytearray([len(ephemeralPublic)]) + ephemeralPublic + bytearray([len(signature)]) + signature
|
|
||||||
apdu = bytearray([0xE0, 0x51, 0x80, 0x00]) + bytearray([len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# walk the device certificates to retrieve the public key to use for authentication
|
|
||||||
index = 0
|
|
||||||
last_pub_key = PublicKey(binascii.unhexlify(issuerKey), raw=True)
|
|
||||||
device_pub_key = None
|
|
||||||
while True:
|
|
||||||
if index == 0:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052000000')))
|
|
||||||
elif index == 1:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052800000')))
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if len(certificate) == 0:
|
|
||||||
break
|
|
||||||
offset = 1
|
|
||||||
certificateHeader = certificate[offset : offset + certificate[offset-1]]
|
|
||||||
offset += certificate[offset-1] + 1
|
|
||||||
certificatePublicKey = certificate[offset : offset + certificate[offset-1]]
|
|
||||||
offset += certificate[offset-1] + 1
|
|
||||||
certificateSignatureArray = certificate[offset : offset + certificate[offset-1]]
|
|
||||||
certificateSignature = last_pub_key.ecdsa_deserialize(bytes(certificateSignatureArray))
|
|
||||||
# first cert contains a header field which holds the certificate's public key role
|
|
||||||
if index == 0:
|
|
||||||
certificateSignedData = bytearray([0x02]) + certificateHeader + certificatePublicKey
|
|
||||||
# Could check if the device certificate is signed by the issuer public key
|
|
||||||
# ephemeral key certificate
|
|
||||||
else:
|
|
||||||
certificateSignedData = bytearray([0x12]) + deviceNonce + nonce + certificatePublicKey
|
|
||||||
if not last_pub_key.ecdsa_verify(bytes(certificateSignedData), certificateSignature):
|
|
||||||
return None
|
|
||||||
last_pub_key = PublicKey(bytes(certificatePublicKey), raw=True)
|
|
||||||
if index == 0:
|
|
||||||
device_pub_key = last_pub_key
|
|
||||||
index = index + 1
|
|
||||||
|
|
||||||
return device_pub_key
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .comm import getDongle
|
|
||||||
from .ecWrapper import PrivateKey, PublicKey
|
|
||||||
import hashlib
|
|
||||||
import struct
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.key == None:
|
|
||||||
raise Exception("Missing endorsement scheme number")
|
|
||||||
if args.key != 1 and args.key != 2:
|
|
||||||
raise Exception("Invalid endorsement scheme number")
|
|
||||||
if args.targetId == None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
if args.issuerKey == None:
|
|
||||||
args.issuerKey = "0490f5c9d15a0134bb019d2afd0bf297149738459706e7ac5be4abc350a1f818057224fce12ec9a65de18ec34d6e8c24db927835ea1692b14c32e9836a75dad609"
|
|
||||||
if (args.privateKey != None) and (args.certificate != None):
|
|
||||||
raise Exception("Cannot specify both certificate and privateKey")
|
|
||||||
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = str(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
publicKey = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId, args.issuerKey)
|
|
||||||
|
|
||||||
if args.certificate == None:
|
|
||||||
apdu = bytearray([0xe0, 0xC0, args.key, 0x00, 0x00])
|
|
||||||
response = dongle.exchange(apdu)
|
|
||||||
print("Public key " + hexstr(response[0:65]))
|
|
||||||
m = hashlib.sha256()
|
|
||||||
m.update(bytes(b"\xff")) # Endorsement role
|
|
||||||
m.update(bytes(response[0:65]))
|
|
||||||
digest = m.digest()
|
|
||||||
signature = publicKey.ecdsa_deserialize(bytes(response[65:]))
|
|
||||||
if not publicKey.ecdsa_verify(bytes(digest), signature, raw=True):
|
|
||||||
raise Exception("Issuer certificate not verified")
|
|
||||||
if args.privateKey != None:
|
|
||||||
privateKey = PrivateKey(bytes(args.privateKey.decode('hex')))
|
|
||||||
dataToSign = bytes(bytearray([0xfe]) + response[0:65])
|
|
||||||
signature = privateKey.ecdsa_sign(bytes(dataToSign))
|
|
||||||
args.certificate = hexstr(privateKey.ecdsa_serialize(signature))
|
|
||||||
|
|
||||||
if args.certificate != None:
|
|
||||||
certificate = bytearray.fromhex(args.certificate)
|
|
||||||
apdu = bytearray([0xe0, 0xC2, 0x00, 0x00, len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
print("Endorsement setup finalized")
|
|
|
@ -1,178 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="""Generate an attestation keypair, using Ledger to sign the Owner
|
|
||||||
certificate.""")
|
|
||||||
parser.add_argument("--url", help="Server URL", default="https://hsmprod.hardwarewallet.com/hsm/process")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--perso", help="""A reference to the personalization key; this is a reference to the specific
|
|
||||||
Issuer keypair used by Ledger to sign the device's Issuer Certificate""", default="perso_11")
|
|
||||||
parser.add_argument("--endorsement", help="""A reference to the endorsement key to use; this is a reference to the
|
|
||||||
specific Owner keypair to be used by Ledger to sign the Owner Certificate""", default="attest_1")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--key", help="Which endorsement scheme to use (1 or 2)", type=auto_int)
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
def serverQuery(request, url):
|
|
||||||
data = request.SerializeToString()
|
|
||||||
urll = urlparse.urlparse(args.url)
|
|
||||||
req = urllib2.Request(args.url, data, {"Content-type": "application/octet-stream" })
|
|
||||||
res = urllib2.urlopen(req)
|
|
||||||
data = res.read()
|
|
||||||
response = Response()
|
|
||||||
response.ParseFromString(data)
|
|
||||||
if len(response.exception) != 0:
|
|
||||||
raise Exception(response.exception)
|
|
||||||
return response
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
if sys.version_info.major == 3:
|
|
||||||
import urllib.request as urllib2
|
|
||||||
import urllib.parse as urlparse
|
|
||||||
else:
|
|
||||||
import urllib2, urlparse
|
|
||||||
from .BlueHSMServer_pb2 import Request, Response, Parameter
|
|
||||||
from .comm import getDongle
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.url == None:
|
|
||||||
raise Exception("No URL specified")
|
|
||||||
if args.perso == None:
|
|
||||||
raise Exception("No personalization specified")
|
|
||||||
if args.endorsement == None:
|
|
||||||
raise Exception("No endorsement specified")
|
|
||||||
if args.key != 1 and args.key != 2:
|
|
||||||
raise Exception("Invalid endorsement scheme number")
|
|
||||||
if args.targetId == None:
|
|
||||||
args.targetId = 0x31000002 # Ledger Blue by default
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
# Identify
|
|
||||||
|
|
||||||
targetid = bytearray(struct.pack('>I', args.targetId))
|
|
||||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# Get nonce and ephemeral key
|
|
||||||
|
|
||||||
request = Request()
|
|
||||||
request.reference = "signEndorsement"
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "persoKey"
|
|
||||||
parameter.name = args.perso
|
|
||||||
|
|
||||||
response = serverQuery(request, args.url)
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
remotePublicKey = response.response[offset : offset + 65]
|
|
||||||
offset += 65
|
|
||||||
nonce = response.response[offset : offset + 8]
|
|
||||||
|
|
||||||
# Initialize chain
|
|
||||||
|
|
||||||
apdu = bytearray([0xe0, 0x50, 0x00, 0x00, 0x08]) + nonce
|
|
||||||
deviceInit = dongle.exchange(apdu)
|
|
||||||
deviceNonce = deviceInit[4 : 4 + 8]
|
|
||||||
|
|
||||||
# Get remote certificate
|
|
||||||
|
|
||||||
request = Request()
|
|
||||||
request.reference = "signEndorsement"
|
|
||||||
request.id = response.id
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "persoKey"
|
|
||||||
parameter.name = args.perso
|
|
||||||
request.parameters = bytes(deviceNonce)
|
|
||||||
|
|
||||||
response = serverQuery(request, args.url)
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
if sys.version_info.major == 2:
|
|
||||||
responseLength = ord(response.response[offset + 1])
|
|
||||||
else:
|
|
||||||
responseLength = response.response[offset + 1]
|
|
||||||
remotePublicKeySignatureLength = responseLength + 2
|
|
||||||
remotePublicKeySignature = response.response[offset : offset + remotePublicKeySignatureLength]
|
|
||||||
|
|
||||||
certificate = bytearray([len(remotePublicKey)]) + remotePublicKey + bytearray([len(remotePublicKeySignature)]) + remotePublicKeySignature
|
|
||||||
apdu = bytearray([0xE0, 0x51, 0x80, 0x00]) + bytearray([len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# Walk the chain
|
|
||||||
|
|
||||||
index = 0
|
|
||||||
while True:
|
|
||||||
if index == 0:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052000000')))
|
|
||||||
elif index == 1:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052800000')))
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if len(certificate) == 0:
|
|
||||||
break
|
|
||||||
request = Request()
|
|
||||||
request.reference = "signEndorsement"
|
|
||||||
request.id = response.id
|
|
||||||
request.parameters = bytes(certificate)
|
|
||||||
serverQuery(request, args.url)
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
# Commit agreement
|
|
||||||
|
|
||||||
request = Request()
|
|
||||||
request.reference = "signEndorsement"
|
|
||||||
request.id = response.id
|
|
||||||
response = serverQuery(request, args.url)
|
|
||||||
|
|
||||||
# Send endorsement request
|
|
||||||
|
|
||||||
apdu = bytearray([0xe0, 0xC0, args.key, 0x00, 0x00])
|
|
||||||
endorsementData = dongle.exchange(apdu)
|
|
||||||
|
|
||||||
request = Request()
|
|
||||||
request.reference = "signEndorsement"
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "endorsementKey"
|
|
||||||
parameter.name = args.endorsement
|
|
||||||
request.parameters = bytes(endorsementData)
|
|
||||||
request.id = response.id
|
|
||||||
response = serverQuery(request, args.url)
|
|
||||||
certificate = bytearray(response.response)
|
|
||||||
|
|
||||||
# Commit endorsement certificate
|
|
||||||
|
|
||||||
apdu = bytearray([0xe0, 0xC2, 0x00, 0x00, len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
print("Endorsement setup finalized")
|
|
|
@ -1,47 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Generate a Custom CA public-private keypair and print it to console.")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def hexstr(bstr):
|
|
||||||
if (sys.version_info.major == 3):
|
|
||||||
return binascii.hexlify(bstr).decode()
|
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
return binascii.hexlify(bstr)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .hexParser import IntelHexParser, IntelHexPrinter
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
|
||||||
import struct
|
|
||||||
import binascii
|
|
||||||
import sys
|
|
||||||
|
|
||||||
get_argparser().parse_args()
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = hexstr(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
print("Public key : %s" % publicKey)
|
|
||||||
print("Private key: %s" % privateKey.serialize())
|
|
|
@ -1,59 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Calculate an application hash from the application's hex file.")
|
|
||||||
parser.add_argument("--hex", help="The application hex file to be hashed")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
def hexstr(bstr):
|
|
||||||
if (sys.version_info.major == 3):
|
|
||||||
return binascii.hexlify(bstr).decode()
|
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
return binascii.hexlify(bstr)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .hexParser import IntelHexParser
|
|
||||||
from .hexParser import IntelHexPrinter
|
|
||||||
import sys
|
|
||||||
import hashlib
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.hex == None:
|
|
||||||
raise Exception("Missing hex filename to hash")
|
|
||||||
|
|
||||||
# parse
|
|
||||||
parser = IntelHexParser(args.hex)
|
|
||||||
|
|
||||||
# prepare data
|
|
||||||
m = hashlib.sha256()
|
|
||||||
# consider areas are ordered by ascending address and non-overlaped
|
|
||||||
for a in parser.getAreas():
|
|
||||||
m.update(a.data)
|
|
||||||
dataToSign = m.digest()
|
|
||||||
|
|
||||||
print(hexstr(dataToSign))
|
|
|
@ -18,129 +18,18 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
import sys
|
|
||||||
import struct
|
import struct
|
||||||
import hashlib
|
import hashlib
|
||||||
import binascii
|
|
||||||
from .ecWrapper import PrivateKey, PublicKey
|
|
||||||
from builtins import int
|
|
||||||
from ecpy.curves import Curve
|
|
||||||
import os
|
|
||||||
#from builtins import str
|
|
||||||
|
|
||||||
LOAD_SEGMENT_CHUNK_HEADER_LENGTH = 3
|
|
||||||
MIN_PADDING_LENGTH = 1
|
|
||||||
SCP_MAC_LENGTH = 0xE
|
|
||||||
|
|
||||||
BOLOS_TAG_APPNAME = 0x01
|
|
||||||
BOLOS_TAG_APPVERSION = 0x02
|
|
||||||
BOLOS_TAG_ICON = 0x03
|
|
||||||
BOLOS_TAG_DERIVEPATH = 0x04
|
|
||||||
BOLOS_TAG_DATASIZE = 0x05
|
|
||||||
BOLOS_TAG_DEPENDENCY = 0x06
|
|
||||||
|
|
||||||
def encodelv(v):
|
|
||||||
l = len(v)
|
|
||||||
s = b""
|
|
||||||
if l < 128:
|
|
||||||
s += struct.pack(">B", l)
|
|
||||||
elif l < 256:
|
|
||||||
s += struct.pack(">B", 0x81)
|
|
||||||
s += struct.pack(">B", l)
|
|
||||||
elif l < 65536:
|
|
||||||
s += struct.pack(">B", 0x82)
|
|
||||||
s += struct.pack(">H", l)
|
|
||||||
else:
|
|
||||||
raise Exception("Unimplemented LV encoding")
|
|
||||||
s += v
|
|
||||||
return s
|
|
||||||
|
|
||||||
def encodetlv(t, v):
|
|
||||||
l = len(v)
|
|
||||||
s = struct.pack(">B", t)
|
|
||||||
if l < 128:
|
|
||||||
s += struct.pack(">B", l)
|
|
||||||
elif l < 256:
|
|
||||||
s += struct.pack(">B", 0x81)
|
|
||||||
s += struct.pack(">B", l)
|
|
||||||
elif l < 65536:
|
|
||||||
s += struct.pack(">B", 0x82)
|
|
||||||
s += struct.pack(">H", l)
|
|
||||||
else:
|
|
||||||
raise Exception("Unimplemented TLV encoding")
|
|
||||||
s += v
|
|
||||||
return s
|
|
||||||
|
|
||||||
def str2bool(v):
|
|
||||||
if v is not None:
|
|
||||||
return v.lower() in ("yes", "true", "t", "1")
|
|
||||||
return False
|
|
||||||
SCP_DEBUG = str2bool(os.getenv("SCP_DEBUG"))
|
|
||||||
|
|
||||||
class HexLoader:
|
class HexLoader:
|
||||||
|
def __init__(self, card, cla=0xF0, secure=False, key=None, relative=True):
|
||||||
def scp_derive_key(self, ecdh_secret, keyindex):
|
|
||||||
retry = 0
|
|
||||||
# di = sha256(i || retrycounter || ecdh secret)
|
|
||||||
while True:
|
|
||||||
sha256 = hashlib.new('sha256')
|
|
||||||
sha256.update(struct.pack(">IB", keyindex, retry))
|
|
||||||
sha256.update(ecdh_secret)
|
|
||||||
|
|
||||||
# compare di with order
|
|
||||||
CURVE_SECP256K1 = Curve.get_curve('secp256k1')
|
|
||||||
if int.from_bytes(sha256.digest(), 'big') < CURVE_SECP256K1.order:
|
|
||||||
break
|
|
||||||
#regenerate a new di satisfying order upper bound
|
|
||||||
retry+=1
|
|
||||||
|
|
||||||
# Pi = di*G
|
|
||||||
privkey = PrivateKey(bytes(sha256.digest()))
|
|
||||||
pubkey = bytearray(privkey.pubkey.serialize(compressed=False))
|
|
||||||
# ki = sha256(Pi)
|
|
||||||
sha256 = hashlib.new('sha256')
|
|
||||||
sha256.update(pubkey)
|
|
||||||
#print ("Key " + str (keyindex) + ": " + sha256.hexdigest())
|
|
||||||
return sha256.digest()
|
|
||||||
|
|
||||||
def __init__(self, card, cla=0xF0, secure=False, mutauth_result=None, relative=True, cleardata_block_len=None):
|
|
||||||
self.card = card
|
self.card = card
|
||||||
self.cla = cla
|
self.cla = cla
|
||||||
self.secure = secure
|
self.secure = secure
|
||||||
self.createappParams = None
|
self.key = key
|
||||||
|
self.iv = "\x00" * 16
|
||||||
#legacy unsecure SCP (pre nanos-1.4, pre blue-2.1)
|
|
||||||
self.max_mtu = 0xFE
|
|
||||||
if not self.card is None:
|
|
||||||
self.max_mtu = min(self.max_mtu, self.card.apduMaxDataSize())
|
|
||||||
self.scpVersion = 2
|
|
||||||
self.key = mutauth_result
|
|
||||||
self.iv = b'\x00' * 16
|
|
||||||
self.relative = relative
|
self.relative = relative
|
||||||
|
|
||||||
#store the aligned block len to be transported if requested
|
|
||||||
self.cleardata_block_len=cleardata_block_len
|
|
||||||
if not (self.cleardata_block_len is None):
|
|
||||||
if not self.card is None:
|
|
||||||
self.cleardata_block_len = min(self.cleardata_block_len, self.card.apduMaxDataSize())
|
|
||||||
|
|
||||||
# try:
|
|
||||||
if type(mutauth_result) is dict and 'ecdh_secret' in mutauth_result:
|
|
||||||
self.scp_enc_key = self.scp_derive_key(mutauth_result['ecdh_secret'], 0)[0:16]
|
|
||||||
self.scp_enc_iv = b"\x00" * 16
|
|
||||||
self.scp_mac_key = self.scp_derive_key(mutauth_result['ecdh_secret'], 1)[0:16]
|
|
||||||
self.scp_mac_iv = b"\x00" * 16
|
|
||||||
self.scpVersion = 3
|
|
||||||
self.max_mtu = 0xFE
|
|
||||||
if not self.card is None:
|
|
||||||
self.max_mtu = min(self.max_mtu, self.card.apduMaxDataSize()&0xF0)
|
|
||||||
|
|
||||||
# except:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def crc16(self, data):
|
def crc16(self, data):
|
||||||
TABLE_CRC16_CCITT = [
|
TABLE_CRC16_CCITT = [
|
||||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
||||||
|
@ -184,122 +73,41 @@ class HexLoader:
|
||||||
return crc
|
return crc
|
||||||
|
|
||||||
def exchange(self, cla, ins, p1, p2, data):
|
def exchange(self, cla, ins, p1, p2, data):
|
||||||
#wrap
|
apdu = bytearray(chr(cla) + chr(ins) + chr(p1) + chr(p2) + chr(len(data))) + bytearray(data)
|
||||||
data = self.scpWrap(data)
|
|
||||||
apdu = bytearray([cla, ins, p1, p2, len(data)]) + bytearray(data)
|
|
||||||
if self.card == None:
|
if self.card == None:
|
||||||
print("%s" % binascii.hexlify(apdu))
|
print str(apdu).encode('hex')
|
||||||
else:
|
else:
|
||||||
# unwrap after exchanged
|
self.card.exchange(apdu)
|
||||||
return self.scpUnwrap(bytes(self.card.exchange(apdu)))
|
|
||||||
|
|
||||||
def scpWrap(self, data):
|
def encryptAES(self, data):
|
||||||
if not self.secure or data is None or len(data) == 0:
|
if not self.secure:
|
||||||
return data
|
return data
|
||||||
|
paddedData = data + '\x80'
|
||||||
if self.scpVersion == 3:
|
while (len(paddedData) % 16) <> 0:
|
||||||
if SCP_DEBUG:
|
paddedData += '\x00'
|
||||||
print(binascii.hexlify(data))
|
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
|
||||||
# ENC
|
encryptedData = cipher.encrypt(str(paddedData))
|
||||||
paddedData = data + b'\x80'
|
self.iv = encryptedData[len(encryptedData) - 16:]
|
||||||
while (len(paddedData) % 16) != 0:
|
|
||||||
paddedData += b'\x00'
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print(binascii.hexlify(paddedData))
|
|
||||||
cipher = AES.new(self.scp_enc_key, AES.MODE_CBC, self.scp_enc_iv)
|
|
||||||
encryptedData = cipher.encrypt(bytes(paddedData))
|
|
||||||
self.scp_enc_iv = encryptedData[-16:]
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print(binascii.hexlify(encryptedData))
|
|
||||||
# MAC
|
|
||||||
cipher = AES.new(self.scp_mac_key, AES.MODE_CBC, self.scp_mac_iv)
|
|
||||||
macData = cipher.encrypt(encryptedData)
|
|
||||||
self.scp_mac_iv = macData[-16:]
|
|
||||||
|
|
||||||
# only append part of the mac
|
|
||||||
encryptedData += self.scp_mac_iv[-SCP_MAC_LENGTH:]
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print(binascii.hexlify(encryptedData))
|
|
||||||
else:
|
|
||||||
paddedData = data + b'\x80'
|
|
||||||
while (len(paddedData) % 16) != 0:
|
|
||||||
paddedData += b'\x00'
|
|
||||||
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print("wrap_old: "+binascii.hexlify(paddedData))
|
|
||||||
encryptedData = cipher.encrypt(paddedData)
|
|
||||||
self.iv = encryptedData[-16:]
|
|
||||||
|
|
||||||
#print (">>")
|
|
||||||
return encryptedData
|
return encryptedData
|
||||||
|
|
||||||
def scpUnwrap(self, data):
|
|
||||||
if not self.secure or data is None or len(data) == 0 or len(data) == 2:
|
|
||||||
return data
|
|
||||||
|
|
||||||
if sys.version_info.major == 3:
|
|
||||||
padding_char = 0x80
|
|
||||||
else:
|
|
||||||
padding_char = chr(0x80)
|
|
||||||
|
|
||||||
if self.scpVersion == 3:
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print(binascii.hexlify(data))
|
|
||||||
# MAC
|
|
||||||
cipher = AES.new(self.scp_mac_key, AES.MODE_CBC, self.scp_mac_iv)
|
|
||||||
macData = cipher.encrypt(data[0:-SCP_MAC_LENGTH])
|
|
||||||
self.scp_mac_iv = macData[-16:]
|
|
||||||
if self.scp_mac_iv[-SCP_MAC_LENGTH:] != data[-SCP_MAC_LENGTH:] :
|
|
||||||
raise BaseException("Invalid SCP MAC")
|
|
||||||
# consume mac
|
|
||||||
data = data[0:-SCP_MAC_LENGTH]
|
|
||||||
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print(binascii.hexlify(data))
|
|
||||||
# ENC
|
|
||||||
cipher = AES.new(self.scp_enc_key, AES.MODE_CBC, self.scp_enc_iv)
|
|
||||||
self.scp_enc_iv = data[-16:]
|
|
||||||
data = cipher.decrypt(data)
|
|
||||||
l = len(data) - 1
|
|
||||||
while (data[l] != padding_char):
|
|
||||||
l-=1
|
|
||||||
if l == -1:
|
|
||||||
raise BaseException("Invalid SCP ENC padding")
|
|
||||||
data = data[0:l]
|
|
||||||
decryptedData = data
|
|
||||||
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print(binascii.hexlify(data))
|
|
||||||
else:
|
|
||||||
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
|
|
||||||
decryptedData = cipher.decrypt(data)
|
|
||||||
if SCP_DEBUG:
|
|
||||||
print("unwrap_old: "+binascii.hexlify(decryptedData))
|
|
||||||
l = len(decryptedData) - 1
|
|
||||||
while (decryptedData[l] != padding_char):
|
|
||||||
l-=1
|
|
||||||
if l == -1:
|
|
||||||
raise BaseException("Invalid SCP ENC padding")
|
|
||||||
decryptedData = decryptedData[0:l]
|
|
||||||
self.iv = data[-16:]
|
|
||||||
|
|
||||||
#print ("<<")
|
|
||||||
return decryptedData
|
|
||||||
|
|
||||||
def selectSegment(self, baseAddress):
|
def selectSegment(self, baseAddress):
|
||||||
data = b'\x05' + struct.pack('>I', baseAddress)
|
data = '\x05' + struct.pack('>I', baseAddress)
|
||||||
|
data = self.encryptAES(data)
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||||
|
|
||||||
def loadSegmentChunk(self, offset, chunk):
|
def loadSegmentChunk(self, offset, chunk):
|
||||||
data = b'\x06' + struct.pack('>H', offset) + chunk
|
data = '\x06' + struct.pack('>H', offset) + chunk
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
data = self.encryptAES(data)
|
||||||
|
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||||
|
|
||||||
def flushSegment(self):
|
def flushSegment(self):
|
||||||
data = b'\x07'
|
data = '\x07'
|
||||||
|
data = self.encryptAES(data)
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||||
|
|
||||||
def crcSegment(self, offsetSegment, lengthSegment, crcExpected):
|
def crcSegment(self, offsetSegment, lengthSegment, crcExpected):
|
||||||
data = b'\x08' + struct.pack('>H', offsetSegment) + struct.pack('>I', lengthSegment) + struct.pack('>H', crcExpected)
|
data = '\x08' + struct.pack('>H', offsetSegment) + struct.pack('>I', lengthSegment) + struct.pack('>H', crcExpected)
|
||||||
|
data = self.encryptAES(data)
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||||
|
|
||||||
def validateTargetId(self, targetId):
|
def validateTargetId(self, targetId):
|
||||||
|
@ -309,132 +117,32 @@ class HexLoader:
|
||||||
def boot(self, bootadr, signature=None):
|
def boot(self, bootadr, signature=None):
|
||||||
# Force jump into Thumb mode
|
# Force jump into Thumb mode
|
||||||
bootadr |= 1
|
bootadr |= 1
|
||||||
data = b'\x09' + struct.pack('>I', bootadr)
|
data = '\x09' + struct.pack('>I', bootadr)
|
||||||
if (signature != None):
|
if (signature != None):
|
||||||
data += struct.pack('>B', len(signature)) + signature
|
data += chr(len(signature)) + signature
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
data = self.encryptAES(data)
|
||||||
|
|
||||||
def commit(self, signature=None):
|
|
||||||
data = b'\x09'
|
|
||||||
if (signature != None):
|
|
||||||
data += struct.pack('>B', len(signature)) + signature
|
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
|
||||||
|
|
||||||
def createAppNoInstallParams(self, appflags, applength, appname, icon=None, path=None, iconOffset=None, iconSize=None, appversion=None):
|
|
||||||
data = b'\x0B' + struct.pack('>I', applength) + struct.pack('>I', appflags) + struct.pack('>B', len(appname)) + appname
|
|
||||||
if iconOffset is None:
|
|
||||||
if not (icon is None):
|
|
||||||
data += struct.pack('>B', len(icon)) + icon
|
|
||||||
else:
|
|
||||||
data += b'\x00'
|
|
||||||
|
|
||||||
if not (path is None):
|
|
||||||
data += struct.pack('>B', len(path)) + path
|
|
||||||
else:
|
|
||||||
data += b'\x00'
|
|
||||||
|
|
||||||
if not iconOffset is None:
|
|
||||||
data += struct.pack('>I', iconOffset) + struct.pack('>H', iconSize)
|
|
||||||
|
|
||||||
if not appversion is None:
|
|
||||||
data += struct.pack('>B', len(appversion)) + appversion
|
|
||||||
|
|
||||||
# in previous version, appparams are not part of the application hash yet
|
|
||||||
self.createappParams = None #data[1:]
|
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||||
|
|
||||||
def createApp(self, code_length, data_length=0, install_params_length=0, flags=0, bootOffset=1):
|
def createApp(self, appflags, applength, appname, icon=None, path=None):
|
||||||
#keep the create app parameters to be included in the load app hash
|
data = '\x0B' + struct.pack('>I', applength) + struct.pack('>I', appflags) + chr(len(appname)) + appname
|
||||||
self.createappParams = struct.pack('>IIIII', code_length, data_length, install_params_length, flags, bootOffset)
|
if (icon != None):
|
||||||
data = b'\x0B' + self.createappParams
|
data += chr(len(icon)) + icon
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
if (path != None):
|
||||||
|
data += chr(len(path)) + path
|
||||||
|
data = self.encryptAES(data)
|
||||||
|
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||||
|
|
||||||
def deleteApp(self, appname):
|
def deleteApp(self, appname):
|
||||||
data = b'\x0C' + struct.pack('>B',len(appname)) + appname
|
data = '\x0C' + chr(len(appname)) + appname
|
||||||
|
data = self.encryptAES(data)
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||||
|
|
||||||
def deleteAppByHash(self, appfullhash):
|
def load(self, erase_u8, max_length_per_apdu, hexAreas, bootaddr):
|
||||||
if len(appfullhash) != 32:
|
|
||||||
raise BaseException("Invalid hash format, sha256 expected")
|
|
||||||
data = b'\x15' + appfullhash
|
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
|
||||||
|
|
||||||
def getVersion(self):
|
|
||||||
data = b'\x10'
|
|
||||||
response = self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
|
||||||
if sys.version_info.major == 2:
|
|
||||||
response = bytearray(response)
|
|
||||||
result = {}
|
|
||||||
offset = 0
|
|
||||||
result['targetId'] = (response[offset] << 24) | (response[offset + 1] << 16) | (response[offset + 2] << 8) | response[offset + 3]
|
|
||||||
offset += 4
|
|
||||||
result['osVersion'] = response[offset + 1 : offset + 1 + response[offset]].decode('utf-8')
|
|
||||||
offset += 1 + response[offset]
|
|
||||||
offset += 1
|
|
||||||
result['flags'] = (response[offset] << 24) | (response[offset + 1] << 16) | (response[offset + 2] << 8) | response[offset + 3]
|
|
||||||
offset += 4
|
|
||||||
result['mcuVersion'] = response[offset + 1 : offset + 1 + response[offset] - 1].decode('utf-8')
|
|
||||||
offset += 1 + response[offset]
|
|
||||||
if (offset < len(response)):
|
|
||||||
result['mcuHash'] = response[offset : offset + 32]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def listApp(self, restart=True):
|
|
||||||
if restart:
|
|
||||||
data = b'\x0E'
|
|
||||||
else:
|
|
||||||
data = b'\x0F'
|
|
||||||
response = self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
|
||||||
if sys.version_info.major == 2:
|
|
||||||
response = bytearray(response)
|
|
||||||
#print binascii.hexlify(response[0])
|
|
||||||
result = []
|
|
||||||
offset = 0
|
|
||||||
if len(response) > 0:
|
|
||||||
if response[0] != 0x01:
|
|
||||||
# support old format
|
|
||||||
while offset != len(response):
|
|
||||||
item = {}
|
|
||||||
offset += 1
|
|
||||||
item['name'] = response[offset + 1 : offset + 1 + response[offset]].decode('utf-8')
|
|
||||||
offset += 1 + response[offset]
|
|
||||||
item['flags'] = (response[offset] << 24) | (response[offset + 1] << 16) | (response[offset + 2] << 8) | response[offset + 3]
|
|
||||||
offset += 4
|
|
||||||
item['hash'] = response[offset : offset + 32]
|
|
||||||
offset += 32
|
|
||||||
result.append(item)
|
|
||||||
else:
|
|
||||||
offset += 1
|
|
||||||
while offset != len(response):
|
|
||||||
item = {}
|
|
||||||
#skip the current entry's size
|
|
||||||
offset += 1
|
|
||||||
item['flags'] = (response[offset] << 24) | (response[offset + 1] << 16) | (response[offset + 2] << 8) | response[offset + 3]
|
|
||||||
offset += 4
|
|
||||||
item['hash_code_data'] = response[offset : offset + 32]
|
|
||||||
offset += 32
|
|
||||||
item['hash'] = response[offset : offset + 32]
|
|
||||||
offset += 32
|
|
||||||
item['name'] = response[offset + 1 : offset + 1 + response[offset]].decode('utf-8')
|
|
||||||
offset += 1 + response[offset]
|
|
||||||
result.append(item)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def load(self, erase_u8, max_length_per_apdu, hexFile, reverse=False, doCRC=True):
|
|
||||||
if (max_length_per_apdu > self.max_mtu):
|
|
||||||
max_length_per_apdu = self.max_mtu
|
|
||||||
initialAddress = 0
|
initialAddress = 0
|
||||||
if self.relative:
|
if (len(hexAreas) <> 0) and self.relative:
|
||||||
initialAddress = hexFile.minAddr()
|
initialAddress = hexAreas[0].getStart()
|
||||||
sha256 = hashlib.new('sha256')
|
sha256 = hashlib.new('sha256')
|
||||||
# stat by hashing the create app params to ensure complete app signature
|
for area in hexAreas:
|
||||||
if self.createappParams:
|
|
||||||
sha256.update(self.createappParams)
|
|
||||||
areas = hexFile.getAreas()
|
|
||||||
if reverse:
|
|
||||||
areas = reversed(hexFile.getAreas())
|
|
||||||
for area in areas:
|
|
||||||
startAddress = area.getStart() - initialAddress
|
startAddress = area.getStart() - initialAddress
|
||||||
data = area.getData()
|
data = area.getData()
|
||||||
self.selectSegment(startAddress)
|
self.selectSegment(startAddress)
|
||||||
|
@ -445,50 +153,23 @@ class HexLoader:
|
||||||
crc = self.crc16(bytearray(data))
|
crc = self.crc16(bytearray(data))
|
||||||
offset = 0
|
offset = 0
|
||||||
length = len(data)
|
length = len(data)
|
||||||
if reverse:
|
|
||||||
offset = length
|
|
||||||
while (length > 0):
|
while (length > 0):
|
||||||
if length > max_length_per_apdu - LOAD_SEGMENT_CHUNK_HEADER_LENGTH - MIN_PADDING_LENGTH - SCP_MAC_LENGTH:
|
if length > max_length_per_apdu:
|
||||||
chunkLen = max_length_per_apdu - LOAD_SEGMENT_CHUNK_HEADER_LENGTH - MIN_PADDING_LENGTH - SCP_MAC_LENGTH
|
chunkLen = max_length_per_apdu
|
||||||
if (chunkLen%16) != 0:
|
|
||||||
chunkLen -= (chunkLen%16)
|
|
||||||
else:
|
else:
|
||||||
chunkLen = length
|
chunkLen = length
|
||||||
|
chunk = data[offset : offset + chunkLen]
|
||||||
if self.cleardata_block_len and chunkLen%self.cleardata_block_len:
|
sha256.update(chunk)
|
||||||
if (chunkLen < self.cleardata_block_len):
|
self.loadSegmentChunk(offset, chunk)
|
||||||
raise Exception("Cannot transport not block aligned data with fixed block len")
|
offset += chunkLen
|
||||||
chunkLen -= chunkLen%self.cleardata_block_len;
|
|
||||||
# padd with 00's when not complete block and performing NENC
|
|
||||||
if reverse:
|
|
||||||
chunk = data[offset-chunkLen : offset]
|
|
||||||
self.loadSegmentChunk(offset-chunkLen, bytes(chunk))
|
|
||||||
else:
|
|
||||||
chunk = data[offset : offset + chunkLen]
|
|
||||||
sha256.update(chunk)
|
|
||||||
self.loadSegmentChunk(offset, bytes(chunk))
|
|
||||||
if reverse:
|
|
||||||
offset -= chunkLen
|
|
||||||
else:
|
|
||||||
offset += chunkLen
|
|
||||||
length -= chunkLen
|
length -= chunkLen
|
||||||
self.flushSegment()
|
self.flushSegment()
|
||||||
if doCRC:
|
self.crcSegment(0, len(data), crc)
|
||||||
self.crcSegment(0, len(data), crc)
|
|
||||||
return sha256.hexdigest()
|
return sha256.hexdigest()
|
||||||
|
|
||||||
def run(self, bootoffset=1, signature=None):
|
def run(self, hexAreas, bootaddr, signature=None):
|
||||||
self.boot(bootoffset, signature)
|
initialAddress = 0
|
||||||
|
if (len(hexAreas) <> 0) and self.relative:
|
||||||
def resetCustomCA(self):
|
initialAddress = hexAreas[0].getStart()
|
||||||
data = b'\x13'
|
self.boot(bootaddr - initialAddress, signature)
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
|
||||||
|
|
||||||
def setupCustomCA(self, name, public):
|
|
||||||
data = b'\x12' + struct.pack('>B',len(name)) + name + struct.pack('>B',len(public)) + public
|
|
||||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
|
||||||
|
|
||||||
def runApp(self, name):
|
|
||||||
data = name
|
|
||||||
self.exchange(self.cla, 0xD8, 0x00, 0x00, data)
|
|
||||||
|
|
||||||
|
|
|
@ -18,202 +18,139 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class IntelHexArea:
|
class IntelHexArea:
|
||||||
def __init__(self, start, data):
|
def __init__(self, start, data):
|
||||||
self.start = start
|
self.start = start
|
||||||
self.data = data
|
self.data = data
|
||||||
|
self.bootAddr = 0
|
||||||
|
|
||||||
def getStart(self):
|
def getStart(self):
|
||||||
return self.start
|
return self.start
|
||||||
|
|
||||||
def getData(self):
|
|
||||||
return self.data
|
|
||||||
|
|
||||||
def insertAreaSorted(areas, area):
|
|
||||||
i=0
|
|
||||||
while i < len(areas):
|
|
||||||
if area.start < areas[i].start:
|
|
||||||
break
|
|
||||||
i+=1
|
|
||||||
#areas = areas[0:i] + [area]
|
|
||||||
areas[i:i] = [area]
|
|
||||||
return areas
|
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
class IntelHexParser:
|
class IntelHexParser:
|
||||||
# order by start address
|
def __init__(self, fileName):
|
||||||
def _addArea(self, area):
|
self.areas = []
|
||||||
self.areas = insertAreaSorted(self.areas, area)
|
lineNumber = 0
|
||||||
|
startZone = None
|
||||||
|
startFirst = None
|
||||||
|
current = None
|
||||||
|
zoneData = ""
|
||||||
|
file = open(fileName, "r")
|
||||||
|
for data in file:
|
||||||
|
lineNumber += 1
|
||||||
|
data = data.rstrip('\r\n')
|
||||||
|
if len(data) == 0:
|
||||||
|
continue
|
||||||
|
if data[0] <> ':':
|
||||||
|
raise Exception("Invalid data at line " + str(lineNumber))
|
||||||
|
data = bytearray(data[1:].decode('hex'))
|
||||||
|
count = data[0]
|
||||||
|
address = (data[1] << 8) + data[2]
|
||||||
|
recordType = data[3]
|
||||||
|
if recordType == 0x00:
|
||||||
|
if startZone == None:
|
||||||
|
raise Exception("Data record but no zone defined at line " + lineNumber)
|
||||||
|
if startFirst == None:
|
||||||
|
startFirst = address
|
||||||
|
current = startFirst
|
||||||
|
if address <> current:
|
||||||
|
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData))
|
||||||
|
zoneData = ""
|
||||||
|
startFirst = address
|
||||||
|
current = address
|
||||||
|
zoneData += data[4:4 + count]
|
||||||
|
current += count
|
||||||
|
if recordType == 0x01:
|
||||||
|
if len(zoneData) <> 0:
|
||||||
|
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData))
|
||||||
|
zoneData = ""
|
||||||
|
startZone = None
|
||||||
|
startFirst = None
|
||||||
|
current = None
|
||||||
|
if recordType == 0x02:
|
||||||
|
raise Exception("Unsupported record 02")
|
||||||
|
if recordType == 0x03:
|
||||||
|
raise Exception("Unsupported record 03")
|
||||||
|
if recordType == 0x04:
|
||||||
|
if len(zoneData) <> 0:
|
||||||
|
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData))
|
||||||
|
zoneData = ""
|
||||||
|
startZone = None
|
||||||
|
startFirst = None
|
||||||
|
current = None
|
||||||
|
startZone = (data[4] << 8) + data[5]
|
||||||
|
if recordType == 0x05:
|
||||||
|
self.bootAddr = ((data[4]&0xFF) << 24) + ((data[5]&0xFF) << 16) + ((data[6]&0xFF) << 8) + (data[7]&0xFF)
|
||||||
|
file.close()
|
||||||
|
|
||||||
def __init__(self, fileName):
|
def getAreas(self):
|
||||||
self.bootAddr = 0
|
return self.areas
|
||||||
self.areas = []
|
|
||||||
lineNumber = 0
|
|
||||||
startZone = None
|
|
||||||
startFirst = None
|
|
||||||
current = None
|
|
||||||
zoneData = b''
|
|
||||||
file = open(fileName, "r")
|
|
||||||
for data in file:
|
|
||||||
lineNumber += 1
|
|
||||||
data = data.rstrip('\r\n')
|
|
||||||
if len(data) == 0:
|
|
||||||
continue
|
|
||||||
if data[0] != ':':
|
|
||||||
raise Exception("Invalid data at line %d" % lineNumber)
|
|
||||||
data = bytearray.fromhex(data[1:])
|
|
||||||
count = data[0]
|
|
||||||
address = (data[1] << 8) + data[2]
|
|
||||||
recordType = data[3]
|
|
||||||
if recordType == 0x00:
|
|
||||||
if startZone == None:
|
|
||||||
raise Exception("Data record but no zone defined at line " + str(lineNumber))
|
|
||||||
if startFirst == None:
|
|
||||||
startFirst = address
|
|
||||||
current = startFirst
|
|
||||||
if address != current:
|
|
||||||
self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
|
|
||||||
zoneData = ""
|
|
||||||
startFirst = address
|
|
||||||
current = address
|
|
||||||
zoneData += data[4:4 + count]
|
|
||||||
current += count
|
|
||||||
if recordType == 0x01:
|
|
||||||
if len(zoneData) != 0:
|
|
||||||
self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
|
|
||||||
zoneData = ""
|
|
||||||
startZone = None
|
|
||||||
startFirst = None
|
|
||||||
current = None
|
|
||||||
if recordType == 0x02:
|
|
||||||
raise Exception("Unsupported record 02")
|
|
||||||
if recordType == 0x03:
|
|
||||||
raise Exception("Unsupported record 03")
|
|
||||||
if recordType == 0x04:
|
|
||||||
if len(zoneData) != 0:
|
|
||||||
self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
|
|
||||||
zoneData = ""
|
|
||||||
startZone = None
|
|
||||||
startFirst = None
|
|
||||||
current = None
|
|
||||||
startZone = (data[4] << 8) + data[5]
|
|
||||||
if recordType == 0x05:
|
|
||||||
self.bootAddr = ((data[4]&0xFF) << 24) + ((data[5]&0xFF) << 16) + ((data[6]&0xFF) << 8) + (data[7]&0xFF)
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
def getAreas(self):
|
def getBootAddr(self):
|
||||||
return self.areas
|
return self.bootAddr
|
||||||
|
|
||||||
def getBootAddr(self):
|
|
||||||
return self.bootAddr
|
|
||||||
|
|
||||||
def maxAddr(self):
|
def maxAddr(self):
|
||||||
addr = 0
|
addr = 0
|
||||||
for a in self.areas:
|
for a in self.areas:
|
||||||
if (a.start+len(a.data) > addr):
|
if (a.start+len(a.data) > addr):
|
||||||
addr = a.start+len(a.data)
|
addr = a.start+len(a.data)
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
def minAddr(self):
|
|
||||||
addr = 0xFFFFFFFF
|
|
||||||
for a in self.areas:
|
|
||||||
if (a.start < addr):
|
|
||||||
addr = a.start
|
|
||||||
return addr
|
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
class IntelHexPrinter:
|
class IntelHexPrinter:
|
||||||
def addArea(self, startaddress, data, insertFirst=False):
|
def __init__(self, parser=None, eol="\r\n"):
|
||||||
#self.areas.append(IntelHexArea(startaddress, data))
|
self.areas = []
|
||||||
if (insertFirst):
|
self.eol = eol
|
||||||
self.areas = [IntelHexArea(startaddress, data)] + self.areas
|
self.bootAddr = 0
|
||||||
else:
|
# build bound to the parser
|
||||||
#order by start address
|
if (parser):
|
||||||
self.areas = insertAreaSorted(self.areas, IntelHexArea(startaddress, data))
|
self.areas = parser.areas
|
||||||
|
self.bootAddr = parser.bootAddr
|
||||||
|
|
||||||
|
def addArea(self, startaddress, data):
|
||||||
|
self.areas.append(IntelHexArea(startaddress, data))
|
||||||
|
|
||||||
def __init__(self, parser=None, eol="\r\n"):
|
def setBootAddr(self, bootAddr):
|
||||||
self.areas = []
|
self.bootAddr = int(bootAddr)
|
||||||
self.eol = eol
|
|
||||||
self.bootAddr = 0
|
|
||||||
# build bound to the parser
|
|
||||||
if (parser):
|
|
||||||
for a in parser.areas:
|
|
||||||
self.addArea(a.start, a.data);
|
|
||||||
self.bootAddr = parser.bootAddr
|
|
||||||
|
|
||||||
def getAreas(self):
|
def checksum(self, bin):
|
||||||
return self.areas
|
cks = 0
|
||||||
|
for b in bin:
|
||||||
|
cks += b
|
||||||
|
cks = (-cks) & 0x0FF
|
||||||
|
return cks
|
||||||
|
|
||||||
def getBootAddr(self):
|
def _emit_binary(self, file, bin):
|
||||||
return self.bootAddr
|
cks = self.checksum(bin)
|
||||||
|
file.write((":" + binascii.hexlify(bin) + hex(0x100+cks)[3:] + self.eol).upper())
|
||||||
|
|
||||||
def maxAddr(self):
|
def writeTo(self, fileName, blocksize=32):
|
||||||
addr = 0
|
file = open(fileName, "w")
|
||||||
for a in self.areas:
|
for area in self.areas:
|
||||||
if (a.start+len(a.data) > addr):
|
off = 0
|
||||||
addr = a.start+len(a.data)
|
# force the emission of selection record at start
|
||||||
return addr
|
oldoff = area.start + 0x10000
|
||||||
|
while off < len(area.data):
|
||||||
|
# emit a offset selection record
|
||||||
|
if ((off & 0xFFFF0000) != (oldoff & 0xFFFF0000) ):
|
||||||
|
self._emit_binary(file, bytearray(("02000004" + hex(0x10000+(area.start>>16))[3:7]).decode('hex')))
|
||||||
|
|
||||||
def minAddr(self):
|
# emit data record
|
||||||
addr = 0xFFFFFFFF
|
if (off+blocksize > len(area.data)):
|
||||||
for a in self.areas:
|
self._emit_binary(file, bytearray((hex(0x100+(len(area.data)-off))[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:len(area.data)])
|
||||||
if (a.start < addr):
|
else:
|
||||||
addr = a.start
|
self._emit_binary(file, bytearray((hex(0x100+blocksize)[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:off+blocksize])
|
||||||
return addr
|
|
||||||
|
|
||||||
def setBootAddr(self, bootAddr):
|
oldoff = off;
|
||||||
self.bootAddr = int(bootAddr)
|
off += blocksize
|
||||||
|
|
||||||
def checksum(self, bin):
|
|
||||||
cks = 0
|
|
||||||
for b in bin:
|
|
||||||
cks += b
|
|
||||||
cks = (-cks) & 0x0FF
|
|
||||||
return cks
|
|
||||||
|
|
||||||
def _emit_binary(self, file, bin):
|
|
||||||
cks = self.checksum(bin)
|
|
||||||
s = (":" + binascii.hexlify(bin) + hex(0x100+cks)[3:] + self.eol).upper()
|
|
||||||
if (file != None):
|
|
||||||
file.write(s)
|
|
||||||
else:
|
|
||||||
print(s)
|
|
||||||
|
|
||||||
def writeTo(self, fileName, blocksize=32):
|
|
||||||
file = None
|
|
||||||
if(fileName != None):
|
|
||||||
file = open(fileName, "w")
|
|
||||||
for area in self.areas:
|
|
||||||
off = 0
|
|
||||||
# force the emission of selection record at start
|
|
||||||
oldoff = area.start + 0x10000
|
|
||||||
while off < len(area.data):
|
|
||||||
# emit a offset selection record
|
|
||||||
if ((off & 0xFFFF0000) != (oldoff & 0xFFFF0000) ):
|
|
||||||
self._emit_binary(file, bytearray(("02000004" + hex(0x10000+(area.start>>16))[3:7]).decode('hex')))
|
|
||||||
|
|
||||||
# emit data record
|
|
||||||
if (off+blocksize > len(area.data)):
|
|
||||||
self._emit_binary(file, bytearray((hex(0x100+(len(area.data)-off))[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:len(area.data)])
|
|
||||||
else:
|
|
||||||
self._emit_binary(file, bytearray((hex(0x100+blocksize)[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:off+blocksize])
|
|
||||||
|
|
||||||
oldoff = off;
|
|
||||||
off += blocksize
|
|
||||||
|
|
||||||
bootAddrHex = hex(0x100000000+self.bootAddr)[3:]
|
bootAddrHex = hex(0x100000000+self.bootAddr)[3:]
|
||||||
s = ":04000005"+bootAddrHex+hex(0x100+self.checksum( bytearray(("04000005"+bootAddrHex).decode('hex'))))[3:]+self.eol
|
file.write(":04000005"+bootAddrHex+hex(0x100+self.checksum( bytearray(("04000005"+bootAddrHex).decode('hex'))))[3:]+self.eol)
|
||||||
if (file != None):
|
|
||||||
file.write(s)
|
|
||||||
else:
|
|
||||||
print(s)
|
|
||||||
|
|
||||||
s = ":00000001FF"+self.eol
|
file.write(":00000001FF"+self.eol)
|
||||||
|
|
||||||
if (file != None):
|
file.close()
|
||||||
file.write(s)
|
|
||||||
else:
|
|
||||||
print(s)
|
|
||||||
|
|
||||||
if (file != None):
|
|
||||||
file.close()
|
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="""
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Using this script undermines the security of the device. Caveat emptor.
|
|
||||||
""")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--id", help="Identity to initialize", type=auto_int)
|
|
||||||
parser.add_argument("--pin", help="Set a PINs to backup the seed for future use")
|
|
||||||
parser.add_argument("--prefix", help="Derivation prefix")
|
|
||||||
parser.add_argument("--passphrase", help="Derivation passphrase")
|
|
||||||
parser.add_argument("--words", help="Derivation phrase")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .hexParser import IntelHexParser, IntelHexPrinter
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
import struct
|
|
||||||
import binascii
|
|
||||||
import sys
|
|
||||||
import getpass
|
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if (args.id is None) or args.id > 2:
|
|
||||||
raise Exception("Missing identity number [0-2]")
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
def enter_if_none_and_normalize(hint, strg):
|
|
||||||
if strg is None: # or len(string) == 0: len 0 is accepted, to specify without being bothered by a message
|
|
||||||
strg = getpass.getpass(hint)
|
|
||||||
if len(strg) != 0 :
|
|
||||||
strg = unicodedata.normalize('NFKD', u''+strg)
|
|
||||||
return strg
|
|
||||||
|
|
||||||
if (args.id < 2):
|
|
||||||
args.pin = enter_if_none_and_normalize("PIN: ", args.pin)
|
|
||||||
if args.pin is None or len(args.pin) == 0:
|
|
||||||
raise Exception("Missing PIN for persistent identity")
|
|
||||||
elif not args.pin is None:
|
|
||||||
raise Exception("Can't set a PIN for the temporary identity")
|
|
||||||
|
|
||||||
args.prefix = enter_if_none_and_normalize("Derivation prefix: ", args.prefix)
|
|
||||||
args.passphrase = enter_if_none_and_normalize("Derivation passphrase: ", args.passphrase)
|
|
||||||
args.words = enter_if_none_and_normalize("Derivation phrase: ", args.words)
|
|
||||||
|
|
||||||
if args.pin:
|
|
||||||
apdudata = bytearray([len(args.pin)]) + bytearray(args.pin, 'utf8')
|
|
||||||
else:
|
|
||||||
apdudata = bytearray([0])
|
|
||||||
|
|
||||||
if args.prefix:
|
|
||||||
apdudata += bytearray([len(args.prefix)]) + bytearray(args.prefix, 'utf8')
|
|
||||||
else:
|
|
||||||
apdudata += bytearray([0])
|
|
||||||
|
|
||||||
if args.passphrase:
|
|
||||||
apdudata += bytearray([len(args.passphrase)]) + bytearray(args.passphrase, 'utf8')
|
|
||||||
else:
|
|
||||||
apdudata += bytearray([0])
|
|
||||||
|
|
||||||
if args.words:
|
|
||||||
apdudata += bytearray([len(args.words)]) + bytearray(args.words, 'utf8')
|
|
||||||
else:
|
|
||||||
apdudata += bytearray([0])
|
|
||||||
|
|
||||||
apdu = bytearray([0xE0, 0xD0, args.id, 0x00, len(apdudata)]) + apdudata
|
|
||||||
dongle.exchange(apdu, timeout=3000)
|
|
|
@ -39,7 +39,7 @@ def wrapCommandAPDU(channel, command, packetSize, ble=False):
|
||||||
blockSize = len(command)
|
blockSize = len(command)
|
||||||
result += command[offset : offset + blockSize]
|
result += command[offset : offset + blockSize]
|
||||||
offset = offset + blockSize
|
offset = offset + blockSize
|
||||||
while offset != len(command):
|
while offset <> len(command):
|
||||||
if not ble:
|
if not ble:
|
||||||
result += struct.pack(">H", channel)
|
result += struct.pack(">H", channel)
|
||||||
result += struct.pack(">BH", 0x05, sequenceIdx)
|
result += struct.pack(">BH", 0x05, sequenceIdx)
|
||||||
|
@ -51,8 +51,8 @@ def wrapCommandAPDU(channel, command, packetSize, ble=False):
|
||||||
result += command[offset : offset + blockSize]
|
result += command[offset : offset + blockSize]
|
||||||
offset = offset + blockSize
|
offset = offset + blockSize
|
||||||
if not ble:
|
if not ble:
|
||||||
while (len(result) % packetSize) != 0:
|
while (len(result) % packetSize) <> 0:
|
||||||
result += b"\x00"
|
result += "\x00"
|
||||||
return bytearray(result)
|
return bytearray(result)
|
||||||
|
|
||||||
def unwrapResponseAPDU(channel, data, packetSize, ble=False):
|
def unwrapResponseAPDU(channel, data, packetSize, ble=False):
|
||||||
|
@ -65,16 +65,16 @@ def unwrapResponseAPDU(channel, data, packetSize, ble=False):
|
||||||
if ((data is None) or (len(data) < 5 + extraHeaderSize + 5)):
|
if ((data is None) or (len(data) < 5 + extraHeaderSize + 5)):
|
||||||
return None
|
return None
|
||||||
if not ble:
|
if not ble:
|
||||||
if struct.unpack(">H", data[offset : offset + 2])[0] != channel:
|
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel:
|
||||||
raise CommException("Invalid channel")
|
raise CommException("Invalid channel")
|
||||||
offset += 2
|
offset += 2
|
||||||
if data[offset] != 0x05:
|
if data[offset] <> 0x05:
|
||||||
raise CommException("Invalid tag")
|
raise CommException("Invalid tag")
|
||||||
offset += 1
|
offset += 1
|
||||||
if struct.unpack(">H", data[offset : offset + 2])[0] != sequenceIdx:
|
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> sequenceIdx:
|
||||||
raise CommException("Invalid sequence")
|
raise CommException("Invalid sequence")
|
||||||
offset += 2
|
offset += 2
|
||||||
responseLength = struct.unpack(">H", data[offset : offset + 2])[0]
|
responseLength = struct.unpack(">H", str(data[offset : offset + 2]))[0]
|
||||||
offset += 2
|
offset += 2
|
||||||
if len(data) < 5 + extraHeaderSize + responseLength:
|
if len(data) < 5 + extraHeaderSize + responseLength:
|
||||||
return None
|
return None
|
||||||
|
@ -84,18 +84,18 @@ def unwrapResponseAPDU(channel, data, packetSize, ble=False):
|
||||||
blockSize = responseLength
|
blockSize = responseLength
|
||||||
result = data[offset : offset + blockSize]
|
result = data[offset : offset + blockSize]
|
||||||
offset += blockSize
|
offset += blockSize
|
||||||
while (len(result) != responseLength):
|
while (len(result) <> responseLength):
|
||||||
sequenceIdx = sequenceIdx + 1
|
sequenceIdx = sequenceIdx + 1
|
||||||
if (offset == len(data)):
|
if (offset == len(data)):
|
||||||
return None
|
return None
|
||||||
if not ble:
|
if not ble:
|
||||||
if struct.unpack(">H", data[offset : offset + 2])[0] != channel:
|
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel:
|
||||||
raise CommException("Invalid channel")
|
raise CommException("Invalid channel")
|
||||||
offset += 2
|
offset += 2
|
||||||
if data[offset] != 0x05:
|
if data[offset] <> 0x05:
|
||||||
raise CommException("Invalid tag")
|
raise CommException("Invalid tag")
|
||||||
offset += 1
|
offset += 1
|
||||||
if struct.unpack(">H", data[offset : offset + 2])[0] != sequenceIdx:
|
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> sequenceIdx:
|
||||||
raise CommException("Invalid sequence")
|
raise CommException("Invalid sequence")
|
||||||
offset += 2
|
offset += 2
|
||||||
if (responseLength - len(result)) > packetSize - 3 - extraHeaderSize:
|
if (responseLength - len(result)) > packetSize - 3 - extraHeaderSize:
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="List all apps on the device.")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--rootPrivateKey", help="""The Signer private key used to establish a Secure Channel
|
|
||||||
(otherwise, a random one will be generated)""")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--deployLegacy", help="Use legacy deployment API", action='store_true')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.targetId is None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
if args.rootPrivateKey is None:
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
if args.deployLegacy:
|
|
||||||
secret = getDeployedSecretV1(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
else:
|
|
||||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
loader = HexLoader(dongle, 0xe0, True, secret)
|
|
||||||
apps = loader.listApp()
|
|
||||||
while len(apps) != 0:
|
|
||||||
print(apps)
|
|
||||||
apps = loader.listApp(False)
|
|
|
@ -17,247 +17,122 @@
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_ALIGNMENT = 1024
|
from secp256k1 import PrivateKey
|
||||||
PAGE_ALIGNMENT = 64
|
from .comm import getDongle
|
||||||
|
from .hexParser import IntelHexParser
|
||||||
|
from .hexLoader import HexLoader
|
||||||
|
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
||||||
import argparse
|
import argparse
|
||||||
|
import struct
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Load an app onto the device from a hex file.")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--fileName", help="The application hex file to be loaded onto the device")
|
|
||||||
parser.add_argument("--icon", help="The icon content to use (hex encoded)")
|
|
||||||
parser.add_argument("--curve", help="""A curve on which BIP 32 derivation is locked ("secp256k1", "prime256r1", or
|
|
||||||
"ed25519"), can be repeated""", action='append')
|
|
||||||
parser.add_argument("--path", help="""A BIP 32 path to which derivation is locked (format decimal a'/b'/c), can be
|
|
||||||
repeated""", action='append')
|
|
||||||
parser.add_argument("--appName", help="The name to give the application after loading it")
|
|
||||||
parser.add_argument("--signature", help="A signature of the application (hex encoded)")
|
|
||||||
parser.add_argument("--signApp", help="Sign application with provided rootPrivateKey", action='store_true')
|
|
||||||
parser.add_argument("--appFlags", help="The application flags", type=auto_int)
|
|
||||||
parser.add_argument("--bootAddr", help="The application's boot address", type=auto_int)
|
|
||||||
parser.add_argument("--rootPrivateKey", help="""The Signer private key used to establish a Secure Channel (otherwise
|
|
||||||
a random one will be generated)""")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--deployLegacy", help="Use legacy deployment API", action='store_true')
|
|
||||||
parser.add_argument("--apilevel", help="Use given API level when interacting with the device", type=auto_int)
|
|
||||||
parser.add_argument("--delete", help="Delete the app with the same name before loading the provided one", action='store_true')
|
|
||||||
parser.add_argument("--params", help="Store icon and install parameters in a parameter section before the code", action='store_true')
|
|
||||||
parser.add_argument("--tlv", help="Use install parameters for all variable length parameters", action='store_true')
|
|
||||||
parser.add_argument("--dataSize", help="The code section's size in the provided hex file (to separate data from code, if not provided the whole allocated NVRAM section for the application will remain readonly.", type=auto_int)
|
|
||||||
parser.add_argument("--appVersion", help="The application version (as a string)")
|
|
||||||
parser.add_argument("--offline", help="Request to only output application load APDUs", action="store_true")
|
|
||||||
parser.add_argument("--installparamsSize", help="The loaded install parameters section size (when parameters are already included within the .hex file.", type=auto_int)
|
|
||||||
parser.add_argument("--tlvraw", help="Add a custom install param with the hextag:hexvalue encoding", action='append')
|
|
||||||
parser.add_argument("--dep", help="Add a dependency over an appname[:appversion]", action='append')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
def auto_int(x):
|
||||||
return int(x, 0)
|
return int(x, 0)
|
||||||
|
|
||||||
def parse_bip32_path(path, apilevel):
|
def parse_bip32_path(path, apilevel):
|
||||||
import struct
|
if len(path) == 0:
|
||||||
if len(path) == 0:
|
return ""
|
||||||
return b""
|
result = ""
|
||||||
result = b""
|
elements = path.split('/')
|
||||||
elements = path.split('/')
|
if apilevel >= 5:
|
||||||
if apilevel >= 5:
|
result = result + chr(len(elements))
|
||||||
result = result + struct.pack('>B', len(elements))
|
for pathElement in elements:
|
||||||
for pathElement in elements:
|
element = pathElement.split('\'')
|
||||||
element = pathElement.split('\'')
|
if len(element) == 1:
|
||||||
if len(element) == 1:
|
result = result + struct.pack(">I", int(element[0]))
|
||||||
result = result + struct.pack(">I", int(element[0]))
|
else:
|
||||||
else:
|
result = result + struct.pack(">I", 0x80000000 | int(element[0]))
|
||||||
result = result + struct.pack(">I", 0x80000000 | int(element[0]))
|
return result
|
||||||
return result
|
|
||||||
|
|
||||||
def string_to_bytes(x):
|
parser = argparse.ArgumentParser()
|
||||||
import sys
|
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||||
if sys.version_info.major == 3:
|
parser.add_argument("--fileName", help="Set the file name to load")
|
||||||
return bytes(x, 'ascii')
|
parser.add_argument("--icon", help="Set the icon content to use (hex encoded)")
|
||||||
else:
|
parser.add_argument("--curve", help="Curve on which the derivation is locked (secp256k1|prime256r1|ed25519), can be repeated", action='append')
|
||||||
return bytes(x)
|
parser.add_argument("--path", help="BIP 32 path to which the derivation is locked (format decimal a'/b'/c), can be repeated", action='append')
|
||||||
|
parser.add_argument("--appName", help="Set the application name")
|
||||||
|
parser.add_argument("--signature", help="Optional application's signature (hex encoded)")
|
||||||
|
parser.add_argument("--appFlags", help="Set the application flags", type=auto_int)
|
||||||
|
parser.add_argument("--bootAddr", help="Set the boot address", type=auto_int)
|
||||||
|
parser.add_argument("--rootPrivateKey", help="Set the root private key")
|
||||||
|
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
||||||
|
parser.add_argument("--deployLegacy", help="Use legacy deployment API", action='store_true')
|
||||||
|
parser.add_argument("--apilevel", help="Use given API level when interacting with the device", type=auto_int)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if args.apilevel == None:
|
||||||
from .ecWrapper import PrivateKey
|
args.apilevel = 5
|
||||||
from .comm import getDongle
|
if args.targetId == None:
|
||||||
from .hexParser import IntelHexParser, IntelHexPrinter
|
args.targetId = 0x31000002
|
||||||
from .hexLoader import HexLoader
|
if args.fileName == None:
|
||||||
from .hexLoader import *
|
raise Exception("Missing fileName")
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
if args.appName == None:
|
||||||
import struct
|
raise Exception("Missing appName")
|
||||||
import binascii
|
if args.appFlags == None:
|
||||||
import sys
|
args.appFlags = 0
|
||||||
|
if args.rootPrivateKey == None:
|
||||||
|
privateKey = PrivateKey()
|
||||||
|
publicKey = str(privateKey.pubkey.serialize(compressed=False)).encode('hex')
|
||||||
|
print "Generated random root public key : " + publicKey
|
||||||
|
args.rootPrivateKey = privateKey.serialize().encode('ascii')
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
parser = IntelHexParser(args.fileName)
|
||||||
|
if args.bootAddr == None:
|
||||||
|
args.bootAddr = parser.getBootAddr()
|
||||||
|
|
||||||
if args.apilevel == None:
|
path = ""
|
||||||
args.apilevel = 5
|
curveMask = 0xff
|
||||||
if args.targetId == None:
|
if args.curve != None:
|
||||||
args.targetId = 0x31000002
|
curveMask = 0x00
|
||||||
if args.fileName == None:
|
for curve in args.curve:
|
||||||
raise Exception("Missing fileName")
|
if curve == 'secp256k1':
|
||||||
if args.appName == None:
|
curveMask |= 0x01
|
||||||
raise Exception("Missing appName")
|
elif curve == 'prime256r1':
|
||||||
if args.appFlags == None:
|
curveMask |= 0x02
|
||||||
args.appFlags = 0
|
elif curve == 'ed25519':
|
||||||
if args.rootPrivateKey == None:
|
curveMask |= 0x04
|
||||||
privateKey = PrivateKey()
|
else:
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
raise Exception("Unknown curve " + curve)
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
|
|
||||||
args.appName = string_to_bytes(args.appName)
|
if args.apilevel >= 5:
|
||||||
|
path += chr(curveMask)
|
||||||
parser = IntelHexParser(args.fileName)
|
if args.path != None:
|
||||||
if args.bootAddr == None:
|
for item in args.path:
|
||||||
args.bootAddr = parser.getBootAddr()
|
if len(item) <> 0:
|
||||||
|
path += parse_bip32_path(item, args.apilevel)
|
||||||
path = b""
|
else:
|
||||||
curveMask = 0xff
|
|
||||||
if args.curve != None:
|
if args.curve != None:
|
||||||
curveMask = 0x00
|
print "Curve not supported using this API level, ignoring"
|
||||||
for curve in args.curve:
|
if args.path != None:
|
||||||
if curve == 'secp256k1':
|
if len(args.path) > 1:
|
||||||
curveMask |= 0x01
|
print "Multiple path levels not supported using this API level, ignoring"
|
||||||
elif curve == 'prime256r1':
|
|
||||||
curveMask |= 0x02
|
|
||||||
elif curve == 'ed25519':
|
|
||||||
curveMask |= 0x04
|
|
||||||
else:
|
|
||||||
raise Exception("Unknown curve " + curve)
|
|
||||||
|
|
||||||
if args.apilevel >= 5:
|
|
||||||
path += struct.pack('>B',curveMask)
|
|
||||||
if args.path != None:
|
|
||||||
for item in args.path:
|
|
||||||
if len(item) != 0:
|
|
||||||
path += parse_bip32_path(item, args.apilevel)
|
|
||||||
else:
|
|
||||||
if args.curve != None:
|
|
||||||
print("Curve not supported using this API level, ignoring")
|
|
||||||
if args.path != None:
|
|
||||||
if len(args.path) > 1:
|
|
||||||
print("Multiple path levels not supported using this API level, ignoring")
|
|
||||||
else:
|
|
||||||
path = parse_bip32_path(args.path[0], args.apilevel)
|
|
||||||
|
|
||||||
if not args.icon is None:
|
|
||||||
args.icon = bytearray.fromhex(args.icon)
|
|
||||||
|
|
||||||
signature = None
|
|
||||||
if not args.signature is None:
|
|
||||||
signature = bytearray.fromhex(args.signature)
|
|
||||||
|
|
||||||
#prepend app's data with the icon content (could also add other various install parameters)
|
|
||||||
printer = IntelHexPrinter(parser)
|
|
||||||
|
|
||||||
# Use of Nested Encryption Key within the SCP protocol is mandartory for upgrades
|
|
||||||
cleardata_block_len=None
|
|
||||||
if args.appFlags & 2:
|
|
||||||
# Not true for scp < 3
|
|
||||||
# if signature is None:
|
|
||||||
# raise BaseException('Upgrades must be signed')
|
|
||||||
|
|
||||||
# ensure data can be decoded with code decryption key without troubles.
|
|
||||||
cleardata_block_len = 16
|
|
||||||
|
|
||||||
if not args.offline:
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
if args.deployLegacy:
|
|
||||||
secret = getDeployedSecretV1(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
else:
|
else:
|
||||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
path = parse_bip32_path(args.path[0], args.apilevel)
|
||||||
|
|
||||||
loader = HexLoader(dongle, 0xe0, not(args.offline), secret, cleardata_block_len=cleardata_block_len)
|
dongle = getDongle(args.apdu)
|
||||||
|
|
||||||
#tlv mode does not support explicit by name removal, would require a list app before to identify the hash to be removed
|
if args.deployLegacy:
|
||||||
if (not (args.appFlags & 2)) and args.delete:
|
secret = getDeployedSecretV1(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
||||||
loader.deleteApp(args.appName)
|
else:
|
||||||
|
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
||||||
|
loader = HexLoader(dongle, 0xe0, True, secret)
|
||||||
|
|
||||||
if (args.tlv):
|
if (not (args.appFlags & 2)):
|
||||||
#if code length is not provided, then consider the whole provided hex file is the code and no data section is split
|
loader.deleteApp(args.appName)
|
||||||
code_length = printer.maxAddr() - printer.minAddr()
|
|
||||||
if not args.dataSize is None:
|
|
||||||
code_length -= args.dataSize
|
|
||||||
else:
|
|
||||||
args.dataSize = 0
|
|
||||||
|
|
||||||
installparams = b""
|
appLength = 0
|
||||||
|
for area in parser.getAreas():
|
||||||
|
appLength += len(area.getData())
|
||||||
|
|
||||||
# express dependency
|
icon = None
|
||||||
if (args.dep):
|
if args.icon != None:
|
||||||
for dep in args.dep:
|
icon = bytearray.fromhex(args.icon)
|
||||||
appname = dep
|
|
||||||
appversion = None
|
|
||||||
# split if version is specified
|
|
||||||
if (dep.find(":") != -1):
|
|
||||||
(appname,appversion) = dep.split(":")
|
|
||||||
depvalue = encodelv(string_to_bytes(appname))
|
|
||||||
if(appversion):
|
|
||||||
depvalue += encodelv(string_to_bytes(appversion))
|
|
||||||
installparams += encodetlv(BOLOS_TAG_DEPENDENCY, depvalue)
|
|
||||||
|
|
||||||
#add raw install parameters as requested
|
signature = None
|
||||||
if (args.tlvraw):
|
if args.signature != None:
|
||||||
for tlvraw in args.tlvraw:
|
signature = bytearray.fromhex(args.signature)
|
||||||
(hextag,hexvalue) = tlvraw.split(":")
|
|
||||||
installparams += encodetlv(int(hextag, 16), binascii.unhexlify(hexvalue))
|
|
||||||
|
|
||||||
if (not (args.appFlags & 2)) and ( args.installparamsSize is None or args.installparamsSize == 0 ):
|
loader.createApp(args.appFlags, appLength, args.appName, icon, path)
|
||||||
#build install parameters
|
hash = loader.load(0x0, 0xE0, parser.getAreas(), args.bootAddr)
|
||||||
#mandatory app name
|
print "Application hash : " + hash
|
||||||
installparams += encodetlv(BOLOS_TAG_APPNAME, args.appName)
|
loader.run(parser.getAreas(), args.bootAddr, signature)
|
||||||
if not args.appVersion is None:
|
|
||||||
installparams += encodetlv(BOLOS_TAG_APPVERSION, string_to_bytes(args.appVersion))
|
|
||||||
if not args.icon is None:
|
|
||||||
installparams += encodetlv(BOLOS_TAG_ICON, bytes(args.icon))
|
|
||||||
if len(path) > 0:
|
|
||||||
installparams += encodetlv(BOLOS_TAG_DERIVEPATH, path)
|
|
||||||
|
|
||||||
# append install parameters to the loaded file
|
|
||||||
param_start = printer.maxAddr()+(PAGE_ALIGNMENT-(args.dataSize%PAGE_ALIGNMENT))%PAGE_ALIGNMENT
|
|
||||||
# only append install param section when not an upgrade as it has already been computed in the encrypted and signed chunk
|
|
||||||
printer.addArea(param_start, installparams)
|
|
||||||
paramsSize = len(installparams)
|
|
||||||
else:
|
|
||||||
paramsSize = args.installparamsSize
|
|
||||||
# split code and install params in the code
|
|
||||||
code_length -= args.installparamsSize
|
|
||||||
# create app
|
|
||||||
#ensure the boot address is an offset
|
|
||||||
if args.bootAddr > printer.minAddr():
|
|
||||||
args.bootAddr -= printer.minAddr()
|
|
||||||
loader.createApp(code_length, args.dataSize, paramsSize, args.appFlags, args.bootAddr|1)
|
|
||||||
elif (args.params):
|
|
||||||
paramsSectionContent = []
|
|
||||||
if not args.icon is None:
|
|
||||||
paramsSectionContent = args.icon
|
|
||||||
#take care of aligning the parameters sections to avoid possible invalid dereference of aligned words in the program nvram.
|
|
||||||
#also use the default MPU alignment
|
|
||||||
param_start = printer.minAddr()-len(paramsSectionContent)-(DEFAULT_ALIGNMENT-(len(paramsSectionContent)%DEFAULT_ALIGNMENT))
|
|
||||||
printer.addArea(param_start, paramsSectionContent)
|
|
||||||
# account for added regions (install parameters, icon ...)
|
|
||||||
appLength = printer.maxAddr() - printer.minAddr()
|
|
||||||
loader.createAppNoInstallParams(args.appFlags, appLength, args.appName, None, path, 0, len(paramsSectionContent), args.appVersion)
|
|
||||||
else:
|
|
||||||
# account for added regions (install parameters, icon ...)
|
|
||||||
appLength = printer.maxAddr() - printer.minAddr()
|
|
||||||
loader.createAppNoInstallParams(args.appFlags, appLength, args.appName, args.icon, path, None, None, args.appVersion)
|
|
||||||
|
|
||||||
|
|
||||||
hash = loader.load(0x0, 0xF0, printer)
|
|
||||||
|
|
||||||
print("Application full hash : " + hash)
|
|
||||||
|
|
||||||
if (signature == None and args.signApp):
|
|
||||||
masterPrivate = PrivateKey(bytes(bytearray.fromhex(args.rootPrivateKey)))
|
|
||||||
signature = masterPrivate.ecdsa_serialize(masterPrivate.ecdsa_sign(bytes(binascii.unhexlify(hash)), raw=True))
|
|
||||||
print("Application signature: " + binascii.hexlify(signature))
|
|
||||||
|
|
||||||
if (args.tlv):
|
|
||||||
loader.commit(signature)
|
|
||||||
else:
|
|
||||||
loader.run(args.bootAddr-printer.minAddr(), signature)
|
|
||||||
|
|
|
@ -16,45 +16,36 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
"""
|
"""
|
||||||
|
from .hexParser import IntelHexParser
|
||||||
|
from .hexLoader import HexLoader
|
||||||
|
from .comm import getDongle
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
def auto_int(x):
|
def auto_int(x):
|
||||||
return int(x, 0)
|
return int(x, 0)
|
||||||
|
|
||||||
def get_argparser():
|
parser = argparse.ArgumentParser()
|
||||||
parser = argparse.ArgumentParser(description="""Load the firmware onto the MCU. The MCU must already be in
|
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||||
bootloader mode.""")
|
parser.add_argument("--fileName", help="Set the file name to load")
|
||||||
parser.add_argument("--targetId", help="The device's target ID", type=auto_int)
|
parser.add_argument("--bootAddr", help="Set the boot address", type=auto_int)
|
||||||
parser.add_argument("--fileName", help="The name of the firmware file to load")
|
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
||||||
parser.add_argument("--bootAddr", help="The firmware's boot address", type=auto_int)
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--reverse", help="Load HEX file in reverse from the highest address to the lowest", action='store_true')
|
|
||||||
parser.add_argument("--nocrc", help="Load HEX file without checking CRC of loaded sections", action='store_true')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
args = parser.parse_args()
|
||||||
from .hexParser import IntelHexParser
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
from .comm import getDongle
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
if args.targetId == None:
|
||||||
|
raise Exception("Missing targetId")
|
||||||
|
if args.fileName == None:
|
||||||
|
raise Exception("Missing fileName")
|
||||||
|
|
||||||
if args.targetId == None:
|
parser = IntelHexParser(args.fileName)
|
||||||
raise Exception("Missing targetId")
|
if args.bootAddr == None:
|
||||||
if args.fileName == None:
|
args.bootAddr = parser.getBootAddr()
|
||||||
raise Exception("Missing fileName")
|
|
||||||
|
|
||||||
parser = IntelHexParser(args.fileName)
|
dongle = getDongle(args.apdu)
|
||||||
if args.bootAddr == None:
|
|
||||||
args.bootAddr = parser.getBootAddr()
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
#relative load
|
||||||
|
loader = HexLoader(dongle, 0xe0, False, None, False)
|
||||||
|
|
||||||
#relative load
|
loader.validateTargetId(args.targetId)
|
||||||
loader = HexLoader(dongle, 0xe0, False, None, False)
|
hash = loader.load(0xFF, 0xF0, parser.getAreas(), args.bootAddr)
|
||||||
|
loader.run(parser.getAreas(), args.bootAddr)
|
||||||
loader.validateTargetId(args.targetId)
|
|
||||||
hash = loader.load(0xFF, 0xF0, parser, reverse=args.reverse, doCRC=(not args.nocrc))
|
|
||||||
loader.run(args.bootAddr)
|
|
||||||
|
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Request the MCU to execute its bootloader.")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--rootPrivateKey", help="""The Signer private key used to establish a Secure Channel (otherwise
|
|
||||||
a random one will be generated)""")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
import binascii
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.targetId == None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
if args.rootPrivateKey == None:
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
loader = HexLoader(dongle, 0xe0, True, secret)
|
|
||||||
loader.exchange(0xE0, 0, 0, 0, loader.encryptAES(b'\xB0'));
|
|
|
@ -1,60 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="""Remove all Custom CA public keys previously enrolled onto the
|
|
||||||
device.""")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--rootPrivateKey", help="""The Signer private key used to establish a Secure Channel (otherwise
|
|
||||||
a random one will be generated)""")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .hexParser import IntelHexParser, IntelHexPrinter
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
|
||||||
import struct
|
|
||||||
import binascii
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.targetId is None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
if args.rootPrivateKey is None:
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
loader = HexLoader(dongle, 0xe0, True, secret)
|
|
||||||
|
|
||||||
loader.resetCustomCA()
|
|
|
@ -1,60 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser("Run an application on the device.")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--rootPrivateKey", help="""The Signer private key used to establish a Secure Channel (otherwise
|
|
||||||
a random one will be generated)""")
|
|
||||||
parser.add_argument("--appName", help="The name of the application to run")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .hexParser import IntelHexParser, IntelHexPrinter
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
|
||||||
import struct
|
|
||||||
import binascii
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.targetId is None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
if args.rootPrivateKey is None:
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
if args.appName is None:
|
|
||||||
raise Exception("Missing appname to run")
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
loader = HexLoader(dongle, 0xe0)
|
|
||||||
|
|
||||||
loader.runApp(args.appName)
|
|
|
@ -17,85 +17,65 @@
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .comm import getDongle
|
||||||
|
from .deployed import getDeployedSecretV2
|
||||||
|
from secp256k1 import PrivateKey
|
||||||
|
from Crypto.Cipher import AES
|
||||||
import argparse
|
import argparse
|
||||||
|
import sys
|
||||||
def get_argparser():
|
import fileinput
|
||||||
parser = argparse.ArgumentParser(description="""Read a sequence of command APDUs from a file and send them to the
|
|
||||||
device. The file must be formatted as hex, with one CAPDU per line.""")
|
|
||||||
parser.add_argument("--fileName", help="The name of the APDU script to load")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--scp", help="Open a Secure Channel to exchange APDU", action='store_true')
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Nano S)", type=auto_int)
|
|
||||||
parser.add_argument("--rootPrivateKey", help="""The Signer private key used to establish a Secure Channel (otherwise
|
|
||||||
a random one will be generated)""")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def hexstr(bstr):
|
|
||||||
if (sys.version_info.major == 3):
|
|
||||||
return binascii.hexlify(bstr).decode()
|
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
return binascii.hexlify(bstr)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def auto_int(x):
|
def auto_int(x):
|
||||||
return int(x, 0)
|
return int(x, 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
parser = argparse.ArgumentParser()
|
||||||
from .comm import getDongle
|
parser.add_argument("--fileName", help="Set the file name to load")
|
||||||
from .deployed import getDeployedSecretV2
|
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
||||||
from .ecWrapper import PrivateKey
|
parser.add_argument("--scp", help="open secure channel to exchange apdu", action='store_true')
|
||||||
from Crypto.Cipher import AES
|
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||||
import sys
|
parser.add_argument("--rootPrivateKey", help="Set the root private key")
|
||||||
import fileinput
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.targetId is None:
|
if args.targetId == None:
|
||||||
args.targetId = 0x31100002
|
args.targetId = 0x31100002
|
||||||
if not args.fileName:
|
if not args.fileName:
|
||||||
#raise Exception("Missing fileName")
|
#raise Exception("Missing fileName")
|
||||||
file = sys.stdin
|
file = sys.stdin
|
||||||
else:
|
else:
|
||||||
file = open(args.fileName, "r")
|
file = open(args.fileName, "r")
|
||||||
|
|
||||||
|
class SCP:
|
||||||
|
def __init__(self, dongle, targetId, rootPrivateKey):
|
||||||
|
self.key = getDeployedSecretV2(dongle, rootPrivateKey, targetId)
|
||||||
|
self.iv = "\x00" * 16;
|
||||||
|
|
||||||
|
def encryptAES(self, data):
|
||||||
|
paddedData = data + '\x80'
|
||||||
|
while (len(paddedData) % 16) <> 0:
|
||||||
|
paddedData += '\x00'
|
||||||
|
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
|
||||||
|
encryptedData = cipher.encrypt(str(paddedData))
|
||||||
|
self.iv = encryptedData[len(encryptedData) - 16:]
|
||||||
|
return encryptedData
|
||||||
|
|
||||||
|
|
||||||
class SCP:
|
dongle = getDongle(args.apdu)
|
||||||
|
if args.scp:
|
||||||
|
if args.rootPrivateKey == None:
|
||||||
|
privateKey = PrivateKey()
|
||||||
|
publicKey = str(privateKey.pubkey.serialize(compressed=False)).encode('hex')
|
||||||
|
print "Generated random root public key : " + publicKey
|
||||||
|
args.rootPrivateKey = privateKey.serialize().encode('ascii')
|
||||||
|
scp = SCP(dongle, args.targetId, bytearray.fromhex(args.rootPrivateKey))
|
||||||
|
|
||||||
def __init__(self, dongle, targetId, rootPrivateKey):
|
for data in file:
|
||||||
secret = getDeployedSecretV2(dongle, rootPrivateKey, targetId)
|
data = data.rstrip('\r\n').decode('hex')
|
||||||
self.loader = HexLoader(dongle, 0xe0, True, secret)
|
|
||||||
|
|
||||||
def encryptAES(self, data):
|
|
||||||
return self.loader.scpWrap(data);
|
|
||||||
|
|
||||||
def decryptAES(self, data):
|
|
||||||
return self.loader.scpUnwrap(data);
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
if args.scp:
|
if args.scp:
|
||||||
if args.rootPrivateKey is None:
|
data = bytearray(data)
|
||||||
privateKey = PrivateKey()
|
if data[4] > 0 and len(data)>5:
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
dongle.exchange(data[0:4] + scp.encryptAES(data[5:5+data[4]]) )
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
scp = SCP(dongle, args.targetId, bytearray.fromhex(args.rootPrivateKey))
|
|
||||||
|
|
||||||
for data in file:
|
|
||||||
data = data.rstrip('\r\n').decode('hex')
|
|
||||||
if len(data) < 5:
|
|
||||||
continue
|
|
||||||
if args.scp:
|
|
||||||
data = bytearray(data)
|
|
||||||
if data[4] > 0 and len(data) > 5:
|
|
||||||
apduData = data[5: 5 + data[4]]
|
|
||||||
apduData = scp.encryptAES(bytes(apduData))
|
|
||||||
result = dongle.exchange(
|
|
||||||
data[0:4] + bytearray([len(apduData)]) + bytearray(apduData))
|
|
||||||
else:
|
|
||||||
result = dongle.exchange(data[0:5])
|
|
||||||
result = scp.decryptAES(str(result))
|
|
||||||
if args.apdu:
|
|
||||||
print("<= Clear " + result.encode('hex'))
|
|
||||||
else:
|
else:
|
||||||
dongle.exchange(bytearray(data))
|
dongle.exchange(data[0:5])
|
||||||
|
else:
|
||||||
|
dongle.exchange(bytearray(data))
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Enroll a Custom CA public key onto the device.")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--rootPrivateKey", help="""The Signer private key used to establish a Secure Channel (otherwise
|
|
||||||
a random one will be generated)""")
|
|
||||||
parser.add_argument("--public", help="The Custom CA public key to be enrolled (hex encoded)")
|
|
||||||
parser.add_argument("--name", help="""The name to assign to the Custom CA (this will be displayed on screen upon
|
|
||||||
auth requests)""")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
from .comm import getDongle
|
|
||||||
from .hexParser import IntelHexParser, IntelHexPrinter
|
|
||||||
from .hexLoader import HexLoader
|
|
||||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
|
||||||
import struct
|
|
||||||
import binascii
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.targetId is None:
|
|
||||||
args.targetId = 0x31000002
|
|
||||||
if args.rootPrivateKey is None:
|
|
||||||
privateKey = PrivateKey()
|
|
||||||
publicKey = binascii.hexlify(privateKey.pubkey.serialize(compressed=False))
|
|
||||||
print("Generated random root public key : %s" % publicKey)
|
|
||||||
args.rootPrivateKey = privateKey.serialize()
|
|
||||||
if args.public is None:
|
|
||||||
raise Exception("Missing public key")
|
|
||||||
if args.name is None:
|
|
||||||
raise Exception("Missing certificate name")
|
|
||||||
|
|
||||||
public = bytearray.fromhex(args.public)
|
|
||||||
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
|
||||||
loader = HexLoader(dongle, 0xe0, True, secret)
|
|
||||||
|
|
||||||
loader.setupCustomCA(args.name, public)
|
|
|
@ -17,56 +17,44 @@
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .hexParser import IntelHexParser
|
||||||
|
from .hexParser import IntelHexPrinter
|
||||||
|
from secp256k1 import PrivateKey
|
||||||
|
import hashlib
|
||||||
|
import binascii
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Sign an application using the provided Custom CA private key.")
|
|
||||||
parser.add_argument("--hex", help="The hex file of the application that is to be signed")
|
|
||||||
parser.add_argument("--key", help="The private key with which to sign the app (hex encoded)")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
def auto_int(x):
|
||||||
return int(x, 0)
|
return int(x, 0)
|
||||||
|
|
||||||
def hexstr(bstr):
|
parser = argparse.ArgumentParser()
|
||||||
if (sys.version_info.major == 3):
|
parser.add_argument("--hex", help="Hex file to be signed")
|
||||||
return binascii.hexlify(bstr).decode()
|
parser.add_argument("--key", help="The private key to sign with (hex encoded)")
|
||||||
if (sys.version_info.major == 2):
|
|
||||||
return binascii.hexlify(bstr)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
args = parser.parse_args()
|
||||||
from .hexParser import IntelHexParser
|
|
||||||
from .hexParser import IntelHexPrinter
|
|
||||||
from .ecWrapper import PrivateKey
|
|
||||||
import hashlib
|
|
||||||
import sys
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
if args.hex == None:
|
||||||
|
raise Exception("Missing hex filename to sign")
|
||||||
|
if args.key == None:
|
||||||
|
raise Exception("Missing private key")
|
||||||
|
|
||||||
if args.hex == None:
|
# parse
|
||||||
raise Exception("Missing hex filename to sign")
|
parser = IntelHexParser(args.hex)
|
||||||
if args.key == None:
|
|
||||||
raise Exception("Missing private key")
|
|
||||||
|
|
||||||
# parse
|
# prepare data
|
||||||
parser = IntelHexParser(args.hex)
|
m = hashlib.sha256()
|
||||||
|
# consider areas are ordered by ascending address and non-overlaped
|
||||||
|
for a in parser.getAreas():
|
||||||
|
m.update(a.data)
|
||||||
|
dataToSign = m.digest()
|
||||||
|
|
||||||
# prepare data
|
MASTER_PRIVATE = bytearray.fromhex(args.key)
|
||||||
m = hashlib.sha256()
|
testMaster = PrivateKey(bytes(MASTER_PRIVATE))
|
||||||
# consider areas are ordered by ascending address and non-overlaped
|
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
||||||
for a in parser.getAreas():
|
|
||||||
m.update(a.data)
|
|
||||||
dataToSign = m.digest()
|
|
||||||
|
|
||||||
MASTER_PRIVATE = bytearray.fromhex(args.key)
|
signature = testMaster.ecdsa_sign(bytes(dataToSign), raw=True)
|
||||||
testMaster = PrivateKey(bytes(MASTER_PRIVATE))
|
|
||||||
#testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
|
||||||
|
|
||||||
signature = testMaster.ecdsa_sign(bytes(dataToSign), raw=True)
|
# test signature before printing it
|
||||||
|
if testMaster.pubkey.ecdsa_verify(dataToSign, signature, raw=True):
|
||||||
# test signature before printing it
|
#print "Signer's public: " + binascii.hexlify(testMasterPublic)
|
||||||
if testMaster.pubkey.ecdsa_verify(dataToSign, signature, raw=True):
|
print testMaster.ecdsa_serialize(signature).encode('hex')
|
||||||
#print("Signer's public: " + binascii.hexlify(testMasterPublic))
|
|
||||||
print(hexstr(testMaster.ecdsa_serialize(signature)))
|
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser("Update the firmware by using Ledger to open a Secure Channel.")
|
|
||||||
parser.add_argument("--url", help="Server URL", default="https://hsmprod.hardwarewallet.com/hsm/process")
|
|
||||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
|
||||||
parser.add_argument("--perso", help="""A reference to the personalization key; this is a reference to the specific
|
|
||||||
Issuer keypair used by Ledger to sign the device's Issuer Certificate""", default="perso_11")
|
|
||||||
parser.add_argument("--firmware", help="A reference to the firmware to load")
|
|
||||||
parser.add_argument("--targetId", help="The device's target ID (default is Ledger Blue)", type=auto_int)
|
|
||||||
parser.add_argument("--firmwareKey", help="A reference to the firmware key to use")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
|
||||||
return int(x, 0)
|
|
||||||
|
|
||||||
def serverQuery(request, url):
|
|
||||||
data = request.SerializeToString()
|
|
||||||
urll = urlparse.urlparse(args.url)
|
|
||||||
req = urllib2.Request(args.url, data, {"Content-type": "application/octet-stream" })
|
|
||||||
res = urllib2.urlopen(req)
|
|
||||||
data = res.read()
|
|
||||||
response = Response()
|
|
||||||
response.ParseFromString(data)
|
|
||||||
if len(response.exception) != 0:
|
|
||||||
raise Exception(response.exception)
|
|
||||||
return response
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
if sys.version_info.major == 3:
|
|
||||||
import urllib.request as urllib2
|
|
||||||
import urllib.parse as urlparse
|
|
||||||
else:
|
|
||||||
import urllib2, urlparse
|
|
||||||
from .BlueHSMServer_pb2 import Request, Response, Parameter
|
|
||||||
from .comm import getDongle
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
if args.url == None:
|
|
||||||
raise Exception("No URL specified")
|
|
||||||
if args.perso == None:
|
|
||||||
raise Exception("No personalization specified")
|
|
||||||
if args.firmware == None:
|
|
||||||
raise Exception("No firmware specified")
|
|
||||||
if args.firmwareKey == None:
|
|
||||||
raise Exception("No firmware key specified")
|
|
||||||
if args.targetId == None:
|
|
||||||
args.targetId = 0x31000002 # Ledger Blue by default
|
|
||||||
|
|
||||||
dongle = getDongle(args.apdu)
|
|
||||||
|
|
||||||
# Identify
|
|
||||||
|
|
||||||
targetid = bytearray(struct.pack('>I', args.targetId))
|
|
||||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# Get nonce and ephemeral key
|
|
||||||
|
|
||||||
request = Request()
|
|
||||||
request.reference = "distributeFirmware11"
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "persoKey"
|
|
||||||
parameter.name = args.perso
|
|
||||||
if args.targetId&0xF == 0x3:
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "scpv2"
|
|
||||||
parameter.name = "dummy"
|
|
||||||
request.largeStack = True
|
|
||||||
|
|
||||||
response = serverQuery(request, args.url)
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
remotePublicKey = response.response[offset : offset + 65]
|
|
||||||
offset += 65
|
|
||||||
nonce = response.response[offset : offset + 8]
|
|
||||||
|
|
||||||
# Initialize chain
|
|
||||||
|
|
||||||
apdu = bytearray([0xe0, 0x50, 0x00, 0x00, 0x08]) + nonce
|
|
||||||
deviceInit = dongle.exchange(apdu)
|
|
||||||
deviceNonce = deviceInit[4 : 4 + 8]
|
|
||||||
|
|
||||||
# Get remote certificate
|
|
||||||
|
|
||||||
request = Request()
|
|
||||||
request.reference = "distributeFirmware11"
|
|
||||||
request.id = response.id
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "persoKey"
|
|
||||||
parameter.name = args.perso
|
|
||||||
if args.targetId&0xF == 0x3:
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "scpv2"
|
|
||||||
parameter.name = "dummy"
|
|
||||||
request.parameters = bytes(deviceNonce)
|
|
||||||
request.largeStack = True
|
|
||||||
|
|
||||||
response = serverQuery(request, args.url)
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
if sys.version_info.major == 2:
|
|
||||||
responseLength = ord(response.response[offset + 1])
|
|
||||||
else:
|
|
||||||
responseLength = response.response[offset + 1]
|
|
||||||
remotePublicKeySignatureLength = responseLength + 2
|
|
||||||
remotePublicKeySignature = response.response[offset : offset + remotePublicKeySignatureLength]
|
|
||||||
|
|
||||||
certificate = bytearray([len(remotePublicKey)]) + remotePublicKey + bytearray([len(remotePublicKeySignature)]) + remotePublicKeySignature
|
|
||||||
apdu = bytearray([0xE0, 0x51, 0x80, 0x00]) + bytearray([len(certificate)]) + certificate
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
|
|
||||||
# Walk the chain
|
|
||||||
|
|
||||||
index = 0
|
|
||||||
while True:
|
|
||||||
if index == 0:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052000000')))
|
|
||||||
elif index == 1:
|
|
||||||
certificate = bytearray(dongle.exchange(bytearray.fromhex('E052800000')))
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if len(certificate) == 0:
|
|
||||||
break
|
|
||||||
request = Request()
|
|
||||||
request.reference = "distributeFirmware11"
|
|
||||||
request.id = response.id
|
|
||||||
request.parameters = bytes(certificate)
|
|
||||||
request.largeStack = True
|
|
||||||
serverQuery(request, args.url)
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
# Commit agreement and send firmware
|
|
||||||
|
|
||||||
request = Request()
|
|
||||||
request.reference = "distributeFirmware11"
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "firmware"
|
|
||||||
parameter.name = args.firmware
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "firmwareKey"
|
|
||||||
parameter.name = args.firmwareKey
|
|
||||||
if args.targetId&0xF == 0x3:
|
|
||||||
parameter = request.remote_parameters.add()
|
|
||||||
parameter.local = False
|
|
||||||
parameter.alias = "scpv2"
|
|
||||||
parameter.name = "dummy"
|
|
||||||
request.id = response.id
|
|
||||||
request.largeStack = True
|
|
||||||
|
|
||||||
response = serverQuery(request, args.url)
|
|
||||||
responseData = bytearray(response.response)
|
|
||||||
|
|
||||||
dongle.exchange(bytearray.fromhex('E053000000'))
|
|
||||||
|
|
||||||
offset = 0
|
|
||||||
while offset < len(responseData):
|
|
||||||
apdu = responseData[offset : offset + 5 + responseData[offset + 4]]
|
|
||||||
dongle.exchange(apdu)
|
|
||||||
offset += 5 + responseData[offset + 4]
|
|
|
@ -17,48 +17,43 @@
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .hexParser import IntelHexParser
|
||||||
|
from .hexParser import IntelHexPrinter
|
||||||
|
from secp256k1 import PublicKey
|
||||||
|
import hashlib
|
||||||
|
import binascii
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser("""Verify that the provided signature is a valid signature of the provided
|
|
||||||
application.""")
|
|
||||||
parser.add_argument("--hex", help="The hex file of the signed application")
|
|
||||||
parser.add_argument("--key", help="The Custom CA public key with which to verify the signature (hex encoded)")
|
|
||||||
parser.add_argument("--signature", help="The signature to be verified (hex encoded)")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def auto_int(x):
|
def auto_int(x):
|
||||||
return int(x, 0)
|
return int(x, 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
parser = argparse.ArgumentParser()
|
||||||
from .hexParser import IntelHexParser
|
parser.add_argument("--hex", help="Hex file to be verify")
|
||||||
from .hexParser import IntelHexPrinter
|
parser.add_argument("--key", help="The public key to verify with (hex encoded)")
|
||||||
from .ecWrapper import PublicKey
|
parser.add_argument("--signature", help="The signature to verify with (hex encoded)")
|
||||||
import hashlib
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.hex == None:
|
if args.hex == None:
|
||||||
raise Exception("Missing hex filename to verify")
|
raise Exception("Missing hex filename to verify")
|
||||||
if args.key == None:
|
if args.key == None:
|
||||||
raise Exception("Missing public key")
|
raise Exception("Missing public key")
|
||||||
if args.signature == None:
|
if args.signature == None:
|
||||||
raise Exception("Missing signature")
|
raise Exception("Missing signature")
|
||||||
|
|
||||||
# parse
|
# parse
|
||||||
parser = IntelHexParser(args.hex)
|
parser = IntelHexParser(args.hex)
|
||||||
|
|
||||||
# prepare data
|
# prepare data
|
||||||
m = hashlib.sha256()
|
m = hashlib.sha256()
|
||||||
# consider areas are ordered by ascending address and non-overlaped
|
# consider areas are ordered by ascending address and non-overlaped
|
||||||
for a in parser.getAreas():
|
for a in parser.getAreas():
|
||||||
m.update(a.data)
|
m.update(a.data)
|
||||||
dataToSign = m.digest()
|
dataToSign = m.digest()
|
||||||
|
|
||||||
publicKey = PublicKey(bytes(bytearray.fromhex(args.key)), raw=True)
|
publicKey = PublicKey(bytes(bytearray.fromhex(args.key)), raw=True)
|
||||||
signature = publicKey.ecdsa_deserialize(bytes(bytearray.fromhex(args.signature)))
|
signature = publicKey.ecdsa_deserialize(bytes(bytearray.fromhex(args.signature)))
|
||||||
if not publicKey.ecdsa_verify(bytes(dataToSign), signature, raw=True):
|
if not publicKey.ecdsa_verify(bytes(dataToSign), signature, raw=True):
|
||||||
raise Exception("Signature not verified")
|
raise Exception("Signature not verified")
|
||||||
|
|
||||||
print("Signature verified")
|
print "Signature verified"
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Verify a message signature created with Endorsement Scheme #1.")
|
|
||||||
parser.add_argument("--key", help="The endorsement public key with which to verify the signature (hex encoded)")
|
|
||||||
parser.add_argument("--codehash", help="The hash of the app associated with the endorsement request (hex encoded)")
|
|
||||||
parser.add_argument("--message", help="The message associated to the endorsement request (hex encoded)")
|
|
||||||
parser.add_argument("--signature", help="The signature to be verified (hex encoded)")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PublicKey
|
|
||||||
import hashlib
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.key == None:
|
|
||||||
raise Exception("Missing public key")
|
|
||||||
if args.codehash == None:
|
|
||||||
raise Exception("Missing code hash")
|
|
||||||
if args.message == None:
|
|
||||||
raise Exception("Missing message")
|
|
||||||
if args.signature == None:
|
|
||||||
raise Exception("Missing signature")
|
|
||||||
|
|
||||||
# prepare data
|
|
||||||
m = hashlib.sha256()
|
|
||||||
m.update(bytes(bytearray.fromhex(args.message)))
|
|
||||||
m.update(bytes(bytearray.fromhex(args.codehash)))
|
|
||||||
digest = m.digest()
|
|
||||||
|
|
||||||
publicKey = PublicKey(bytes(bytearray.fromhex(args.key)), raw=True)
|
|
||||||
signature = publicKey.ecdsa_deserialize(bytes(bytearray.fromhex(args.signature)))
|
|
||||||
if not publicKey.ecdsa_verify(bytes(digest), signature, raw=True):
|
|
||||||
raise Exception("Endorsement not verified")
|
|
||||||
|
|
||||||
print("Endorsement verified")
|
|
|
@ -1,59 +0,0 @@
|
||||||
"""
|
|
||||||
*******************************************************************************
|
|
||||||
* Ledger Blue
|
|
||||||
* (c) 2016 Ledger
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
********************************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(description="Verify a message signature created with Endorsement Scheme #2.")
|
|
||||||
parser.add_argument("--key", help="The endorsement public key with which to verify the signature (hex encoded)")
|
|
||||||
parser.add_argument("--codehash", help="The hash of the app associated with the endorsement request (hex encoded)")
|
|
||||||
parser.add_argument("--message", help="The message associated to the endorsement request (hex encoded)")
|
|
||||||
parser.add_argument("--signature", help="The signature to be verified (hex encoded)")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .ecWrapper import PublicKey
|
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
|
|
||||||
if args.key == None:
|
|
||||||
raise Exception("Missing public key")
|
|
||||||
if args.codehash == None:
|
|
||||||
raise Exception("Missing code hash")
|
|
||||||
if args.message == None:
|
|
||||||
raise Exception("Missing message")
|
|
||||||
if args.signature == None:
|
|
||||||
raise Exception("Missing signature")
|
|
||||||
|
|
||||||
# prepare data
|
|
||||||
tweak = hmac.new(bytes(bytearray.fromhex(args.codehash)), bytes(bytearray.fromhex(args.key)), hashlib.sha256).digest()
|
|
||||||
m = hashlib.sha256()
|
|
||||||
m.update(bytes(bytearray.fromhex(args.message)))
|
|
||||||
digest = m.digest()
|
|
||||||
|
|
||||||
publicKey = PublicKey(bytes(bytearray.fromhex(args.key)), raw=True)
|
|
||||||
publicKey.tweak_add(bytes(tweak))
|
|
||||||
signature = publicKey.ecdsa_deserialize(bytes(bytearray.fromhex(args.signature)))
|
|
||||||
if not publicKey.ecdsa_verify(bytes(digest), signature, raw=True):
|
|
||||||
raise Exception("Endorsement not verified")
|
|
||||||
|
|
||||||
print("Endorsement verified")
|
|
6
setup.py
6
setup.py
|
@ -5,17 +5,19 @@ from setuptools import setup, find_packages
|
||||||
from os.path import dirname, join
|
from os.path import dirname, join
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
os.environ['SECP_BUNDLED_EXPERIMENTAL'] = "1"
|
||||||
|
|
||||||
here = dirname(__file__)
|
here = dirname(__file__)
|
||||||
setup(
|
setup(
|
||||||
name='ledgerblue',
|
name='ledgerblue',
|
||||||
version='0.1.17',
|
version='0.1.6',
|
||||||
author='Ledger',
|
author='Ledger',
|
||||||
author_email='hello@ledger.fr',
|
author_email='hello@ledger.fr',
|
||||||
description='Python library to communicate with Ledger Blue/Nano S',
|
description='Python library to communicate with Ledger Blue/Nano S',
|
||||||
long_description=open(join(here, 'README.md')).read(),
|
long_description=open(join(here, 'README.md')).read(),
|
||||||
url='https://github.com/LedgerHQ/blue-loader-python',
|
url='https://github.com/LedgerHQ/blue-loader-python',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
install_requires=['hidapi>=0.7.99', 'protobuf>=2.6.1', 'pycrypto>=2.6.1', 'future', 'ecpy>=0.8.1', 'pillow>=3.4.0', 'python-u2flib-host>=3.0.2'],
|
install_requires=['hidapi>=0.7.99', 'secp256k1>=0.12.1', 'pycrypto>=2.6.1'],
|
||||||
extras_require = {
|
extras_require = {
|
||||||
'smartcard': [ 'python-pyscard>=1.6.12-4build1' ]
|
'smartcard': [ 'python-pyscard>=1.6.12-4build1' ]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue