Compare commits
49 Commits
Author | SHA1 | Date |
---|---|---|
|
c1a5abc6a9 | |
|
ab589175f3 | |
|
ede307f9d0 | |
|
01ac906758 | |
|
3242f93051 | |
|
1e9458ca58 | |
|
3a8a32a2ee | |
|
e834117229 | |
|
ad385223a3 | |
|
9914b3746a | |
|
2abf1cd983 | |
|
ed1e3a4dac | |
|
b66f66b9bf | |
|
a9611d412a | |
|
b23172e440 | |
|
bf1e3453cc | |
|
214cd9783c | |
|
1ad7bb24e7 | |
|
ba72aa7fb0 | |
|
09ad4fd1e2 | |
|
310442593d | |
|
ae02835796 | |
|
53a76b055e | |
|
3738a758e3 | |
|
69c42dabfb | |
|
e73a8a6389 | |
|
e728a7b57b | |
|
0e7d0378d1 | |
|
67a0a424a3 | |
|
2af852d628 | |
|
c2feef8cb4 | |
|
32c600e622 | |
|
c6e5961227 | |
|
c60427dc00 | |
|
43168d0292 | |
|
689ffb81ab | |
|
a6c7c9ed7f | |
|
ee42ea581f | |
|
78e64131d2 | |
|
72b6a4378d | |
|
48f071cafa | |
|
78a3579514 | |
|
cf74f4836c | |
|
0a4f7c4c91 | |
|
dcf012d862 | |
|
b56ff7cbe1 | |
|
b5f80ac8f0 | |
|
58bc25a2bc | |
|
4eefb8a7f7 |
39
README.md
39
README.md
|
@ -1,13 +1,44 @@
|
|||
# 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.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
```
|
||||
virtualenv ledger
|
||||
source ledger/bin/activate
|
||||
SECP_BUNDLED_EXPERIMENTAL=1 pip install secp256k1
|
||||
pip install git+https://github.com/LedgerHQ/blue-loader-python.git
|
||||
pip install ledgerblue
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# 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.
|
|
@ -0,0 +1,19 @@
|
|||
# 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)
|
|
@ -0,0 +1,26 @@
|
|||
# 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.
|
|
@ -0,0 +1 @@
|
|||
sphinx_argparse
|
|
@ -0,0 +1,13 @@
|
|||
/* 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
# -*- 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)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
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
|
|
@ -0,0 +1,212 @@
|
|||
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
|
|
@ -0,0 +1,22 @@
|
|||
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):
|
||||
if self.debug:
|
||||
print "=> %s" % hexlify(apdu)
|
||||
print("=> %s" % hexlify(apdu))
|
||||
apdu = wrapCommandAPDU(0, apdu, DEFAULT_BLE_CHUNK, True)
|
||||
offset = 0
|
||||
while(offset < len(apdu)):
|
||||
|
@ -78,8 +78,8 @@ class BLEDongle(Dongle):
|
|||
sw = (result[swOffset] << 8) + result[swOffset + 1]
|
||||
response = result[dataStart : dataLength + dataStart]
|
||||
if self.debug:
|
||||
print "<= %s%.2x" % (hexlify(response), sw)
|
||||
if sw <> 0x9000:
|
||||
print("<= %s%.2x" % (hexlify(response), sw))
|
||||
if sw != 0x9000:
|
||||
raise CommException("Invalid status %04x" % sw, sw)
|
||||
return response
|
||||
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
# 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)
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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
|
|
@ -0,0 +1,168 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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 .commException import CommException
|
||||
from .ledgerWrapper import wrapCommandAPDU, unwrapResponseAPDU
|
||||
from .Dongle import *
|
||||
from binascii import hexlify
|
||||
import hid
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from .commU2F import getDongle as getDongleU2F
|
||||
from .commHTTP import getDongle as getDongleHTTP
|
||||
import hid
|
||||
|
||||
try:
|
||||
from smartcard.Exceptions import NoCardException
|
||||
from smartcard.System import readers
|
||||
from smartcard.util import toHexString, toBytes
|
||||
SCARD = True
|
||||
except ImportError:
|
||||
SCARD = False
|
||||
|
||||
class DongleWait(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def waitFirstResponse(self, timeout):
|
||||
pass
|
||||
|
||||
class Dongle(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def exchange(self, apdu, timeout=20000):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def setWaitImpl(self, waitImpl):
|
||||
self.waitImpl = waitImpl
|
||||
APDUGEN=None
|
||||
if "APDUGEN" in os.environ and len(os.environ["APDUGEN"]) != 0:
|
||||
APDUGEN=os.environ["APDUGEN"]
|
||||
# Force use of U2F if required
|
||||
U2FKEY=None
|
||||
if "U2FKEY" in os.environ and len(os.environ["U2FKEY"]) != 0:
|
||||
U2FKEY=os.environ["U2FKEY"]
|
||||
# Force use of MCUPROXY if required
|
||||
MCUPROXY=None
|
||||
if "MCUPROXY" in os.environ and len(os.environ["MCUPROXY"]) != 0:
|
||||
MCUPROXY=os.environ["MCUPROXY"]
|
||||
|
||||
# Force use of MCUPROXY if required
|
||||
PCSC=None
|
||||
if "PCSC" in os.environ and len(os.environ["PCSC"]) != 0:
|
||||
PCSC=os.environ["PCSC"]
|
||||
if PCSC:
|
||||
try:
|
||||
from smartcard.Exceptions import NoCardException
|
||||
from smartcard.System import readers
|
||||
from smartcard.util import toHexString, toBytes
|
||||
except ImportError:
|
||||
PCSC = False
|
||||
|
||||
class HIDDongleHIDAPI(Dongle, DongleWait):
|
||||
|
||||
|
@ -62,20 +62,25 @@ class HIDDongleHIDAPI(Dongle, DongleWait):
|
|||
self.waitImpl = self
|
||||
self.opened = True
|
||||
|
||||
def exchange(self, apdu, timeout=20000):
|
||||
def exchange(self, apdu, timeout=TIMEOUT):
|
||||
if APDUGEN:
|
||||
print("%s" % hexstr(apdu))
|
||||
return
|
||||
|
||||
if self.debug:
|
||||
print "=> %s" % hexlify(apdu)
|
||||
print("HID => %s" % hexstr(apdu))
|
||||
if self.ledger:
|
||||
apdu = wrapCommandAPDU(0x0101, apdu, 64)
|
||||
padSize = len(apdu) % 64
|
||||
tmp = apdu
|
||||
if padSize <> 0:
|
||||
if padSize != 0:
|
||||
tmp.extend([0] * (64 - padSize))
|
||||
offset = 0
|
||||
while(offset <> len(tmp)):
|
||||
while(offset != len(tmp)):
|
||||
data = tmp[offset:offset + 64]
|
||||
data = bytearray([0]) + data
|
||||
self.device.write(data)
|
||||
if self.device.write(data) < 0:
|
||||
raise BaseException("Error while writing")
|
||||
offset += 64
|
||||
dataLength = 0
|
||||
dataStart = 2
|
||||
|
@ -114,9 +119,14 @@ class HIDDongleHIDAPI(Dongle, DongleWait):
|
|||
sw = (result[swOffset] << 8) + result[swOffset + 1]
|
||||
response = result[dataStart : dataLength + dataStart]
|
||||
if self.debug:
|
||||
print "<= %s%.2x" % (hexlify(response), sw)
|
||||
if sw <> 0x9000:
|
||||
raise CommException("Invalid status %04x" % sw, sw)
|
||||
print("HID <= %s%.2x" % (hexstr(response), sw))
|
||||
if sw != 0x9000:
|
||||
possibleCause = "Unknown reason"
|
||||
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
|
||||
|
||||
def waitFirstResponse(self, timeout):
|
||||
|
@ -127,9 +137,12 @@ class HIDDongleHIDAPI(Dongle, DongleWait):
|
|||
if not len(data):
|
||||
if time.time() - start > timeout:
|
||||
raise CommException("Timeout")
|
||||
time.sleep(0.02)
|
||||
time.sleep(0.0001)
|
||||
return bytearray(data)
|
||||
|
||||
def apduMaxDataSize(self):
|
||||
return 255
|
||||
|
||||
def close(self):
|
||||
if self.opened:
|
||||
try:
|
||||
|
@ -146,15 +159,15 @@ class DongleSmartcard(Dongle):
|
|||
self.waitImpl = self
|
||||
self.opened = True
|
||||
|
||||
def exchange(self, apdu, timeout=20000):
|
||||
def exchange(self, apdu, timeout=TIMEOUT):
|
||||
if self.debug:
|
||||
print "=> %s" % hexlify(apdu)
|
||||
print("SC => %s" % hexstr(apdu))
|
||||
response, sw1, sw2 = self.device.transmit(toBytes(hexlify(apdu)))
|
||||
sw = (sw1 << 8) | sw2
|
||||
if self.debug:
|
||||
print "<= %s%.2x" % (toHexString(response).replace(" ", ""), sw)
|
||||
if sw <> 0x9000:
|
||||
raise CommException("Invalid status %04x" % sw, sw)
|
||||
print("SC <= %s%.2x" % (hexstr(response).replace(" ", ""), sw))
|
||||
if sw != 0x9000:
|
||||
raise CommException("Invalid status %04x" % sw, sw, bytearray(response))
|
||||
return bytearray(response)
|
||||
|
||||
def close(self):
|
||||
|
@ -166,24 +179,32 @@ class DongleSmartcard(Dongle):
|
|||
self.opened = False
|
||||
|
||||
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
|
||||
hidDevicePath = None
|
||||
ledger = True
|
||||
for hidDevice in hid.enumerate(0, 0):
|
||||
if hidDevice['vendor_id'] == 0x2c97:
|
||||
hidDevicePath = hidDevice['path']
|
||||
if ('interface_number' in hidDevice and hidDevice['interface_number'] == 0) or ('usage_page' in hidDevice and hidDevice['usage_page'] == 0xffa0):
|
||||
hidDevicePath = hidDevice['path']
|
||||
if hidDevicePath is not None:
|
||||
dev = hid.device()
|
||||
dev.open_path(hidDevicePath)
|
||||
dev.set_nonblocking(True)
|
||||
return HIDDongleHIDAPI(dev, ledger, debug)
|
||||
if SCARD:
|
||||
if PCSC:
|
||||
connection = None
|
||||
for reader in readers():
|
||||
try:
|
||||
connection = reader.createConnection()
|
||||
connection.connect()
|
||||
if selectCommand <> None:
|
||||
if selectCommand != None:
|
||||
response, sw1, sw2 = connection.transmit(toBytes("00A4040010FF4C4547522E57414C5430312E493031"))
|
||||
sw = (sw1 << 8) | sw2
|
||||
if sw == 0x9000:
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
class CommException(Exception):
|
||||
|
||||
def __init__(self, message, sw=0x6f00):
|
||||
def __init__(self, message, sw=0x6f00, data=None):
|
||||
self.message = message
|
||||
self.sw = sw
|
||||
self.data = data
|
||||
|
||||
def __str__(self):
|
||||
buf = "Exception : " + self.message
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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)
|
|
@ -0,0 +1,333 @@
|
|||
# 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,39 +17,67 @@
|
|||
********************************************************************************
|
||||
"""
|
||||
|
||||
from secp256k1 import PrivateKey
|
||||
from .comm import getDongle
|
||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
||||
from .hexLoader import HexLoader
|
||||
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):
|
||||
return int(x, 0)
|
||||
return int(x, 0)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||
parser.add_argument("--appName", help="Set the application name")
|
||||
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')
|
||||
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 = parser.parse_args()
|
||||
args = get_argparser().parse_args()
|
||||
|
||||
if args.appName == None:
|
||||
raise Exception("Missing appName")
|
||||
if args.targetId == None:
|
||||
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 and args.appHash == None:
|
||||
raise Exception("Missing appName or appHash")
|
||||
if args.appName != None and args.appHash != None:
|
||||
raise Exception("Set either appName or appHash")
|
||||
|
||||
dongle = getDongle(args.apdu)
|
||||
if args.appName != None:
|
||||
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.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)
|
||||
loader.deleteApp(args.appName)
|
||||
if args.appHash != None:
|
||||
if (sys.version_info.major == 3):
|
||||
args.appHash = bytes(args.appHash,'ascii')
|
||||
if (sys.version_info.major == 2):
|
||||
args.appHash = bytes(args.appHash)
|
||||
args.appHash = bytearray.fromhex(args.appHash)
|
||||
|
||||
|
||||
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,18 +17,22 @@
|
|||
********************************************************************************
|
||||
"""
|
||||
|
||||
from secp256k1 import PrivateKey, PublicKey
|
||||
from .ecWrapper import PrivateKey, PublicKey
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexLoader import HexLoader
|
||||
import binascii
|
||||
|
||||
def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
||||
testMaster = PrivateKey(bytes(masterPrivate))
|
||||
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
||||
targetid = bytearray(struct.pack('>I', targetid))
|
||||
|
||||
if targetId&0xF != 0x1:
|
||||
raise BaseException("Target ID does not support SCP V1")
|
||||
|
||||
# identify
|
||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
||||
dongle.exchange(apdu)
|
||||
|
@ -38,13 +42,13 @@ def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
|||
cardKey = batch_info[5:5 + batch_info[4]]
|
||||
|
||||
# if not found, get another pair
|
||||
#if cardKey <> testMasterPublic:
|
||||
#if cardKey != testMasterPublic:
|
||||
# raise Exception("Invalid batch public key")
|
||||
|
||||
# provide the ephemeral certificate
|
||||
ephemeralPrivate = PrivateKey()
|
||||
ephemeralPublic = bytearray(ephemeralPrivate.pubkey.serialize(compressed=False))
|
||||
print "Using ephemeral key " + str(ephemeralPublic).encode('hex')
|
||||
print("Using ephemeral key %s" %binascii.hexlify(ephemeralPublic))
|
||||
signature = testMaster.ecdsa_sign(bytes(ephemeralPublic))
|
||||
signature = testMaster.ecdsa_serialize(signature)
|
||||
certificate = bytearray([len(ephemeralPublic)]) + ephemeralPublic + bytearray([len(signature)]) + signature
|
||||
|
@ -63,7 +67,7 @@ def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
|||
if not last_pub_key.ecdsa_verify(bytes(certificatePublic), certificateSignature):
|
||||
if index == 0:
|
||||
# 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:
|
||||
raise Exception("Broken certificate chain")
|
||||
last_pub_key = PublicKey(bytes(certificatePublic), raw=True)
|
||||
|
@ -72,12 +76,15 @@ def getDeployedSecretV1(dongle, masterPrivate, targetid):
|
|||
# Commit device ECDH channel
|
||||
dongle.exchange(bytearray.fromhex('E053000000'))
|
||||
secret = last_pub_key.ecdh(bytes(ephemeralPrivate.serialize().decode('hex')))
|
||||
return str(secret[0:16])
|
||||
return secret[0:16]
|
||||
|
||||
def getDeployedSecretV2(dongle, masterPrivate, targetid):
|
||||
def getDeployedSecretV2(dongle, masterPrivate, targetId, signerCertChain=None, ecdh_secret_format=None):
|
||||
testMaster = PrivateKey(bytes(masterPrivate))
|
||||
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
|
||||
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
|
||||
|
@ -91,21 +98,26 @@ def getDeployedSecretV2(dongle, masterPrivate, targetid):
|
|||
deviceNonce = auth_info[4:12]
|
||||
|
||||
# if not found, get another pair
|
||||
#if cardKey <> testMasterPublic:
|
||||
#if cardKey != testMasterPublic:
|
||||
# raise Exception("Invalid batch public key")
|
||||
|
||||
print "Using test master key " + str(testMasterPublic).encode('hex')
|
||||
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)
|
||||
if (signerCertChain):
|
||||
for cert in signerCertChain:
|
||||
apdu = bytearray([0xE0, 0x51, 0x00, 0x00]) + bytearray([len(cert)]) + cert
|
||||
dongle.exchange(apdu)
|
||||
else:
|
||||
print("Using test master key %s " % binascii.hexlify(testMasterPublic))
|
||||
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))
|
||||
print "Using ephemeral key " + str(ephemeralPublic).encode('hex')
|
||||
print("Using ephemeral key %s" %binascii.hexlify(ephemeralPublic))
|
||||
dataToSign = bytes(bytearray([0x11]) + nonce + deviceNonce + ephemeralPublic)
|
||||
signature = testMaster.ecdsa_sign(bytes(dataToSign))
|
||||
signature = testMaster.ecdsa_serialize(signature)
|
||||
|
@ -115,40 +127,50 @@ def getDeployedSecretV2(dongle, masterPrivate, targetid):
|
|||
|
||||
# walk the device certificates to retrieve the public key to use for authentication
|
||||
index = 0
|
||||
last_pub_key = PublicKey(bytes(testMasterPublic), raw=True)
|
||||
last_dev_pub_key = PublicKey(bytes(testMasterPublic), 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
|
||||
break
|
||||
if len(certificate) == 0:
|
||||
break
|
||||
offset = 1
|
||||
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))
|
||||
certificateSignature = last_dev_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):
|
||||
if not last_dev_pub_key.ecdsa_verify(bytes(certificateSignedData), certificateSignature):
|
||||
if index == 0:
|
||||
# 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:
|
||||
raise Exception("Broken certificate chain")
|
||||
last_pub_key = PublicKey(bytes(certificatePublicKey), raw=True)
|
||||
last_dev_pub_key = PublicKey(bytes(certificatePublicKey), raw=True)
|
||||
index = index + 1
|
||||
|
||||
# Commit device ECDH channel
|
||||
dongle.exchange(bytearray.fromhex('E053000000'))
|
||||
secret = last_pub_key.ecdh(bytes(ephemeralPrivate.serialize().decode('hex')))
|
||||
return str(secret[0:16])
|
||||
secret = last_dev_pub_key.ecdh(binascii.unhexlify(ephemeralPrivate.serialize()))
|
||||
|
||||
#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
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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)
|
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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)
|
|
@ -0,0 +1,168 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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")
|
|
@ -0,0 +1,178 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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")
|
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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())
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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,18 +18,129 @@
|
|||
"""
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
import sys
|
||||
import struct
|
||||
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:
|
||||
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.cla = cla
|
||||
self.secure = secure
|
||||
self.key = key
|
||||
self.iv = "\x00" * 16
|
||||
self.createappParams = None
|
||||
|
||||
#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
|
||||
|
||||
#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):
|
||||
TABLE_CRC16_CCITT = [
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
||||
|
@ -73,41 +184,122 @@ class HexLoader:
|
|||
return crc
|
||||
|
||||
def exchange(self, cla, ins, p1, p2, data):
|
||||
apdu = bytearray(chr(cla) + chr(ins) + chr(p1) + chr(p2) + chr(len(data))) + bytearray(data)
|
||||
#wrap
|
||||
data = self.scpWrap(data)
|
||||
apdu = bytearray([cla, ins, p1, p2, len(data)]) + bytearray(data)
|
||||
if self.card == None:
|
||||
print str(apdu).encode('hex')
|
||||
print("%s" % binascii.hexlify(apdu))
|
||||
else:
|
||||
self.card.exchange(apdu)
|
||||
# unwrap after exchanged
|
||||
return self.scpUnwrap(bytes(self.card.exchange(apdu)))
|
||||
|
||||
def encryptAES(self, data):
|
||||
if not self.secure:
|
||||
def scpWrap(self, data):
|
||||
if not self.secure or data is None or len(data) == 0:
|
||||
return 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:]
|
||||
|
||||
if self.scpVersion == 3:
|
||||
if SCP_DEBUG:
|
||||
print(binascii.hexlify(data))
|
||||
# ENC
|
||||
paddedData = data + b'\x80'
|
||||
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
|
||||
|
||||
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):
|
||||
data = '\x05' + struct.pack('>I', baseAddress)
|
||||
data = self.encryptAES(data)
|
||||
data = b'\x05' + struct.pack('>I', baseAddress)
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||
|
||||
def loadSegmentChunk(self, offset, chunk):
|
||||
data = '\x06' + struct.pack('>H', offset) + chunk
|
||||
data = self.encryptAES(data)
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||
data = b'\x06' + struct.pack('>H', offset) + chunk
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||
|
||||
def flushSegment(self):
|
||||
data = '\x07'
|
||||
data = self.encryptAES(data)
|
||||
data = b'\x07'
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||
|
||||
def crcSegment(self, offsetSegment, lengthSegment, crcExpected):
|
||||
data = '\x08' + struct.pack('>H', offsetSegment) + struct.pack('>I', lengthSegment) + struct.pack('>H', crcExpected)
|
||||
data = self.encryptAES(data)
|
||||
data = b'\x08' + struct.pack('>H', offsetSegment) + struct.pack('>I', lengthSegment) + struct.pack('>H', crcExpected)
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||
|
||||
def validateTargetId(self, targetId):
|
||||
|
@ -117,32 +309,132 @@ class HexLoader:
|
|||
def boot(self, bootadr, signature=None):
|
||||
# Force jump into Thumb mode
|
||||
bootadr |= 1
|
||||
data = '\x09' + struct.pack('>I', bootadr)
|
||||
data = b'\x09' + struct.pack('>I', bootadr)
|
||||
if (signature != None):
|
||||
data += chr(len(signature)) + signature
|
||||
data = self.encryptAES(data)
|
||||
data += struct.pack('>B', len(signature)) + signature
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, 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)
|
||||
|
||||
def createApp(self, appflags, applength, appname, icon=None, path=None):
|
||||
data = '\x0B' + struct.pack('>I', applength) + struct.pack('>I', appflags) + chr(len(appname)) + appname
|
||||
if (icon != None):
|
||||
data += chr(len(icon)) + icon
|
||||
if (path != None):
|
||||
data += chr(len(path)) + path
|
||||
data = self.encryptAES(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):
|
||||
#keep the create app parameters to be included in the load app hash
|
||||
self.createappParams = struct.pack('>IIIII', code_length, data_length, install_params_length, flags, bootOffset)
|
||||
data = b'\x0B' + self.createappParams
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||
|
||||
def deleteApp(self, appname):
|
||||
data = '\x0C' + chr(len(appname)) + appname
|
||||
data = self.encryptAES(data)
|
||||
data = b'\x0C' + struct.pack('>B',len(appname)) + appname
|
||||
self.exchange(self.cla, 0x00, 0x00, 0x00, data)
|
||||
|
||||
def load(self, erase_u8, max_length_per_apdu, hexAreas, bootaddr):
|
||||
def deleteAppByHash(self, appfullhash):
|
||||
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
|
||||
if (len(hexAreas) <> 0) and self.relative:
|
||||
initialAddress = hexAreas[0].getStart()
|
||||
if self.relative:
|
||||
initialAddress = hexFile.minAddr()
|
||||
sha256 = hashlib.new('sha256')
|
||||
for area in hexAreas:
|
||||
# stat by hashing the create app params to ensure complete app signature
|
||||
if self.createappParams:
|
||||
sha256.update(self.createappParams)
|
||||
areas = hexFile.getAreas()
|
||||
if reverse:
|
||||
areas = reversed(hexFile.getAreas())
|
||||
for area in areas:
|
||||
startAddress = area.getStart() - initialAddress
|
||||
data = area.getData()
|
||||
self.selectSegment(startAddress)
|
||||
|
@ -153,23 +445,50 @@ class HexLoader:
|
|||
crc = self.crc16(bytearray(data))
|
||||
offset = 0
|
||||
length = len(data)
|
||||
if reverse:
|
||||
offset = length
|
||||
while (length > 0):
|
||||
if length > max_length_per_apdu:
|
||||
chunkLen = max_length_per_apdu
|
||||
if length > max_length_per_apdu - LOAD_SEGMENT_CHUNK_HEADER_LENGTH - MIN_PADDING_LENGTH - SCP_MAC_LENGTH:
|
||||
chunkLen = max_length_per_apdu - LOAD_SEGMENT_CHUNK_HEADER_LENGTH - MIN_PADDING_LENGTH - SCP_MAC_LENGTH
|
||||
if (chunkLen%16) != 0:
|
||||
chunkLen -= (chunkLen%16)
|
||||
else:
|
||||
chunkLen = length
|
||||
chunk = data[offset : offset + chunkLen]
|
||||
sha256.update(chunk)
|
||||
self.loadSegmentChunk(offset, chunk)
|
||||
offset += chunkLen
|
||||
|
||||
if self.cleardata_block_len and chunkLen%self.cleardata_block_len:
|
||||
if (chunkLen < self.cleardata_block_len):
|
||||
raise Exception("Cannot transport not block aligned data with fixed block len")
|
||||
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
|
||||
self.flushSegment()
|
||||
self.crcSegment(0, len(data), crc)
|
||||
if doCRC:
|
||||
self.crcSegment(0, len(data), crc)
|
||||
return sha256.hexdigest()
|
||||
|
||||
def run(self, hexAreas, bootaddr, signature=None):
|
||||
initialAddress = 0
|
||||
if (len(hexAreas) <> 0) and self.relative:
|
||||
initialAddress = hexAreas[0].getStart()
|
||||
self.boot(bootaddr - initialAddress, signature)
|
||||
|
||||
def run(self, bootoffset=1, signature=None):
|
||||
self.boot(bootoffset, signature)
|
||||
|
||||
def resetCustomCA(self):
|
||||
data = b'\x13'
|
||||
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,139 +18,202 @@
|
|||
"""
|
||||
|
||||
class IntelHexArea:
|
||||
def __init__(self, start, data):
|
||||
self.start = start
|
||||
self.data = data
|
||||
self.bootAddr = 0
|
||||
def __init__(self, start, data):
|
||||
self.start = start
|
||||
self.data = data
|
||||
|
||||
def getStart(self):
|
||||
return self.start
|
||||
def getStart(self):
|
||||
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:
|
||||
def __init__(self, fileName):
|
||||
self.areas = []
|
||||
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()
|
||||
# order by start address
|
||||
def _addArea(self, area):
|
||||
self.areas = insertAreaSorted(self.areas, area)
|
||||
|
||||
def getAreas(self):
|
||||
return self.areas
|
||||
def __init__(self, fileName):
|
||||
self.bootAddr = 0
|
||||
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 getBootAddr(self):
|
||||
return self.bootAddr
|
||||
def getAreas(self):
|
||||
return self.areas
|
||||
|
||||
def getBootAddr(self):
|
||||
return self.bootAddr
|
||||
|
||||
def maxAddr(self):
|
||||
addr = 0
|
||||
for a in self.areas:
|
||||
if (a.start+len(a.data) > addr):
|
||||
addr = a.start+len(a.data)
|
||||
return addr
|
||||
def maxAddr(self):
|
||||
addr = 0
|
||||
for a in self.areas:
|
||||
if (a.start+len(a.data) > addr):
|
||||
addr = a.start+len(a.data)
|
||||
return addr
|
||||
|
||||
def minAddr(self):
|
||||
addr = 0xFFFFFFFF
|
||||
for a in self.areas:
|
||||
if (a.start < addr):
|
||||
addr = a.start
|
||||
return addr
|
||||
|
||||
import binascii
|
||||
|
||||
class IntelHexPrinter:
|
||||
def __init__(self, parser=None, eol="\r\n"):
|
||||
self.areas = []
|
||||
self.eol = eol
|
||||
self.bootAddr = 0
|
||||
# build bound to the parser
|
||||
if (parser):
|
||||
self.areas = parser.areas
|
||||
self.bootAddr = parser.bootAddr
|
||||
|
||||
def addArea(self, startaddress, data):
|
||||
self.areas.append(IntelHexArea(startaddress, data))
|
||||
def addArea(self, startaddress, data, insertFirst=False):
|
||||
#self.areas.append(IntelHexArea(startaddress, data))
|
||||
if (insertFirst):
|
||||
self.areas = [IntelHexArea(startaddress, data)] + self.areas
|
||||
else:
|
||||
#order by start address
|
||||
self.areas = insertAreaSorted(self.areas, IntelHexArea(startaddress, data))
|
||||
|
||||
def setBootAddr(self, bootAddr):
|
||||
self.bootAddr = int(bootAddr)
|
||||
def __init__(self, parser=None, eol="\r\n"):
|
||||
self.areas = []
|
||||
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 checksum(self, bin):
|
||||
cks = 0
|
||||
for b in bin:
|
||||
cks += b
|
||||
cks = (-cks) & 0x0FF
|
||||
return cks
|
||||
def getAreas(self):
|
||||
return self.areas
|
||||
|
||||
def _emit_binary(self, file, bin):
|
||||
cks = self.checksum(bin)
|
||||
file.write((":" + binascii.hexlify(bin) + hex(0x100+cks)[3:] + self.eol).upper())
|
||||
def getBootAddr(self):
|
||||
return self.bootAddr
|
||||
|
||||
def writeTo(self, fileName, blocksize=32):
|
||||
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')))
|
||||
def maxAddr(self):
|
||||
addr = 0
|
||||
for a in self.areas:
|
||||
if (a.start+len(a.data) > addr):
|
||||
addr = a.start+len(a.data)
|
||||
return addr
|
||||
|
||||
# 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])
|
||||
def minAddr(self):
|
||||
addr = 0xFFFFFFFF
|
||||
for a in self.areas:
|
||||
if (a.start < addr):
|
||||
addr = a.start
|
||||
return addr
|
||||
|
||||
oldoff = off;
|
||||
off += blocksize
|
||||
def setBootAddr(self, bootAddr):
|
||||
self.bootAddr = int(bootAddr)
|
||||
|
||||
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:]
|
||||
file.write(":04000005"+bootAddrHex+hex(0x100+self.checksum( bytearray(("04000005"+bootAddrHex).decode('hex'))))[3:]+self.eol)
|
||||
bootAddrHex = hex(0x100000000+self.bootAddr)[3:]
|
||||
s = ":04000005"+bootAddrHex+hex(0x100+self.checksum( bytearray(("04000005"+bootAddrHex).decode('hex'))))[3:]+self.eol
|
||||
if (file != None):
|
||||
file.write(s)
|
||||
else:
|
||||
print(s)
|
||||
|
||||
file.write(":00000001FF"+self.eol)
|
||||
s = ":00000001FF"+self.eol
|
||||
|
||||
file.close()
|
||||
|
||||
if (file != None):
|
||||
file.write(s)
|
||||
else:
|
||||
print(s)
|
||||
|
||||
if (file != None):
|
||||
file.close()
|
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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)
|
||||
result += command[offset : offset + blockSize]
|
||||
offset = offset + blockSize
|
||||
while offset <> len(command):
|
||||
while offset != len(command):
|
||||
if not ble:
|
||||
result += struct.pack(">H", channel)
|
||||
result += struct.pack(">BH", 0x05, sequenceIdx)
|
||||
|
@ -51,8 +51,8 @@ def wrapCommandAPDU(channel, command, packetSize, ble=False):
|
|||
result += command[offset : offset + blockSize]
|
||||
offset = offset + blockSize
|
||||
if not ble:
|
||||
while (len(result) % packetSize) <> 0:
|
||||
result += "\x00"
|
||||
while (len(result) % packetSize) != 0:
|
||||
result += b"\x00"
|
||||
return bytearray(result)
|
||||
|
||||
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)):
|
||||
return None
|
||||
if not ble:
|
||||
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel:
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != channel:
|
||||
raise CommException("Invalid channel")
|
||||
offset += 2
|
||||
if data[offset] <> 0x05:
|
||||
if data[offset] != 0x05:
|
||||
raise CommException("Invalid tag")
|
||||
offset += 1
|
||||
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> sequenceIdx:
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != sequenceIdx:
|
||||
raise CommException("Invalid sequence")
|
||||
offset += 2
|
||||
responseLength = struct.unpack(">H", str(data[offset : offset + 2]))[0]
|
||||
responseLength = struct.unpack(">H", data[offset : offset + 2])[0]
|
||||
offset += 2
|
||||
if len(data) < 5 + extraHeaderSize + responseLength:
|
||||
return None
|
||||
|
@ -84,18 +84,18 @@ def unwrapResponseAPDU(channel, data, packetSize, ble=False):
|
|||
blockSize = responseLength
|
||||
result = data[offset : offset + blockSize]
|
||||
offset += blockSize
|
||||
while (len(result) <> responseLength):
|
||||
while (len(result) != responseLength):
|
||||
sequenceIdx = sequenceIdx + 1
|
||||
if (offset == len(data)):
|
||||
return None
|
||||
if not ble:
|
||||
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel:
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != channel:
|
||||
raise CommException("Invalid channel")
|
||||
offset += 2
|
||||
if data[offset] <> 0x05:
|
||||
if data[offset] != 0x05:
|
||||
raise CommException("Invalid tag")
|
||||
offset += 1
|
||||
if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> sequenceIdx:
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != sequenceIdx:
|
||||
raise CommException("Invalid sequence")
|
||||
offset += 2
|
||||
if (responseLength - len(result)) > packetSize - 3 - extraHeaderSize:
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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,122 +17,247 @@
|
|||
********************************************************************************
|
||||
"""
|
||||
|
||||
from secp256k1 import PrivateKey
|
||||
from .comm import getDongle
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexLoader import HexLoader
|
||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
||||
DEFAULT_ALIGNMENT = 1024
|
||||
PAGE_ALIGNMENT = 64
|
||||
|
||||
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):
|
||||
return int(x, 0)
|
||||
return int(x, 0)
|
||||
|
||||
def parse_bip32_path(path, apilevel):
|
||||
if len(path) == 0:
|
||||
return ""
|
||||
result = ""
|
||||
elements = path.split('/')
|
||||
if apilevel >= 5:
|
||||
result = result + chr(len(elements))
|
||||
for pathElement in elements:
|
||||
element = pathElement.split('\'')
|
||||
if len(element) == 1:
|
||||
result = result + struct.pack(">I", int(element[0]))
|
||||
else:
|
||||
result = result + struct.pack(">I", 0x80000000 | int(element[0]))
|
||||
return result
|
||||
import struct
|
||||
if len(path) == 0:
|
||||
return b""
|
||||
result = b""
|
||||
elements = path.split('/')
|
||||
if apilevel >= 5:
|
||||
result = result + struct.pack('>B', len(elements))
|
||||
for pathElement in elements:
|
||||
element = pathElement.split('\'')
|
||||
if len(element) == 1:
|
||||
result = result + struct.pack(">I", int(element[0]))
|
||||
else:
|
||||
result = result + struct.pack(">I", 0x80000000 | int(element[0]))
|
||||
return result
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||
parser.add_argument("--fileName", help="Set the file name to load")
|
||||
parser.add_argument("--icon", help="Set the icon content to use (hex encoded)")
|
||||
parser.add_argument("--curve", help="Curve on which the derivation is locked (secp256k1|prime256r1|ed25519), can be repeated", action='append')
|
||||
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)
|
||||
def string_to_bytes(x):
|
||||
import sys
|
||||
if sys.version_info.major == 3:
|
||||
return bytes(x, 'ascii')
|
||||
else:
|
||||
return bytes(x)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.apilevel == None:
|
||||
args.apilevel = 5
|
||||
if args.targetId == None:
|
||||
args.targetId = 0x31000002
|
||||
if args.fileName == None:
|
||||
raise Exception("Missing fileName")
|
||||
if args.appName == None:
|
||||
raise Exception("Missing appName")
|
||||
if args.appFlags == None:
|
||||
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')
|
||||
if __name__ == '__main__':
|
||||
from .ecWrapper import PrivateKey
|
||||
from .comm import getDongle
|
||||
from .hexParser import IntelHexParser, IntelHexPrinter
|
||||
from .hexLoader import HexLoader
|
||||
from .hexLoader import *
|
||||
from .deployed import getDeployedSecretV1, getDeployedSecretV2
|
||||
import struct
|
||||
import binascii
|
||||
import sys
|
||||
|
||||
parser = IntelHexParser(args.fileName)
|
||||
if args.bootAddr == None:
|
||||
args.bootAddr = parser.getBootAddr()
|
||||
args = get_argparser().parse_args()
|
||||
|
||||
path = ""
|
||||
curveMask = 0xff
|
||||
if args.curve != None:
|
||||
curveMask = 0x00
|
||||
for curve in args.curve:
|
||||
if curve == 'secp256k1':
|
||||
curveMask |= 0x01
|
||||
elif curve == 'prime256r1':
|
||||
curveMask |= 0x02
|
||||
elif curve == 'ed25519':
|
||||
curveMask |= 0x04
|
||||
else:
|
||||
raise Exception("Unknown curve " + curve)
|
||||
if args.apilevel == None:
|
||||
args.apilevel = 5
|
||||
if args.targetId == None:
|
||||
args.targetId = 0x31000002
|
||||
if args.fileName == None:
|
||||
raise Exception("Missing fileName")
|
||||
if args.appName == None:
|
||||
raise Exception("Missing appName")
|
||||
if args.appFlags == None:
|
||||
args.appFlags = 0
|
||||
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()
|
||||
|
||||
if args.apilevel >= 5:
|
||||
path += chr(curveMask)
|
||||
if args.path != None:
|
||||
for item in args.path:
|
||||
if len(item) <> 0:
|
||||
path += parse_bip32_path(item, args.apilevel)
|
||||
else:
|
||||
args.appName = string_to_bytes(args.appName)
|
||||
|
||||
parser = IntelHexParser(args.fileName)
|
||||
if args.bootAddr == None:
|
||||
args.bootAddr = parser.getBootAddr()
|
||||
|
||||
path = b""
|
||||
curveMask = 0xff
|
||||
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"
|
||||
curveMask = 0x00
|
||||
for curve in args.curve:
|
||||
if curve == 'secp256k1':
|
||||
curveMask |= 0x01
|
||||
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:
|
||||
path = parse_bip32_path(args.path[0], args.apilevel)
|
||||
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
|
||||
|
||||
dongle = getDongle(args.apdu)
|
||||
loader = HexLoader(dongle, 0xe0, not(args.offline), secret, cleardata_block_len=cleardata_block_len)
|
||||
|
||||
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)
|
||||
#tlv mode does not support explicit by name removal, would require a list app before to identify the hash to be removed
|
||||
if (not (args.appFlags & 2)) and args.delete:
|
||||
loader.deleteApp(args.appName)
|
||||
|
||||
if (not (args.appFlags & 2)):
|
||||
loader.deleteApp(args.appName)
|
||||
if (args.tlv):
|
||||
#if code length is not provided, then consider the whole provided hex file is the code and no data section is split
|
||||
code_length = printer.maxAddr() - printer.minAddr()
|
||||
if not args.dataSize is None:
|
||||
code_length -= args.dataSize
|
||||
else:
|
||||
args.dataSize = 0
|
||||
|
||||
appLength = 0
|
||||
for area in parser.getAreas():
|
||||
appLength += len(area.getData())
|
||||
installparams = b""
|
||||
|
||||
icon = None
|
||||
if args.icon != None:
|
||||
icon = bytearray.fromhex(args.icon)
|
||||
# express dependency
|
||||
if (args.dep):
|
||||
for dep in args.dep:
|
||||
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)
|
||||
|
||||
signature = None
|
||||
if args.signature != None:
|
||||
signature = bytearray.fromhex(args.signature)
|
||||
#add raw install parameters as requested
|
||||
if (args.tlvraw):
|
||||
for tlvraw in args.tlvraw:
|
||||
(hextag,hexvalue) = tlvraw.split(":")
|
||||
installparams += encodetlv(int(hextag, 16), binascii.unhexlify(hexvalue))
|
||||
|
||||
loader.createApp(args.appFlags, appLength, args.appName, icon, path)
|
||||
hash = loader.load(0x0, 0xE0, parser.getAreas(), args.bootAddr)
|
||||
print "Application hash : " + hash
|
||||
loader.run(parser.getAreas(), args.bootAddr, signature)
|
||||
if (not (args.appFlags & 2)) and ( args.installparamsSize is None or args.installparamsSize == 0 ):
|
||||
#build install parameters
|
||||
#mandatory app name
|
||||
installparams += encodetlv(BOLOS_TAG_APPNAME, args.appName)
|
||||
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,36 +16,45 @@
|
|||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexLoader import HexLoader
|
||||
from .comm import getDongle
|
||||
|
||||
import argparse
|
||||
|
||||
def auto_int(x):
|
||||
return int(x, 0)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||
parser.add_argument("--fileName", help="Set the file name to load")
|
||||
parser.add_argument("--bootAddr", help="Set the boot address", type=auto_int)
|
||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
||||
def get_argparser():
|
||||
parser = argparse.ArgumentParser(description="""Load the firmware onto the MCU. The MCU must already be in
|
||||
bootloader mode.""")
|
||||
parser.add_argument("--targetId", help="The device's target ID", type=auto_int)
|
||||
parser.add_argument("--fileName", help="The name of the firmware file to load")
|
||||
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
|
||||
|
||||
args = parser.parse_args()
|
||||
if __name__ == '__main__':
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexLoader import HexLoader
|
||||
from .comm import getDongle
|
||||
|
||||
if args.targetId == None:
|
||||
raise Exception("Missing targetId")
|
||||
if args.fileName == None:
|
||||
raise Exception("Missing fileName")
|
||||
args = get_argparser().parse_args()
|
||||
|
||||
parser = IntelHexParser(args.fileName)
|
||||
if args.bootAddr == None:
|
||||
args.bootAddr = parser.getBootAddr()
|
||||
if args.targetId == None:
|
||||
raise Exception("Missing targetId")
|
||||
if args.fileName == None:
|
||||
raise Exception("Missing fileName")
|
||||
|
||||
dongle = getDongle(args.apdu)
|
||||
parser = IntelHexParser(args.fileName)
|
||||
if args.bootAddr == None:
|
||||
args.bootAddr = parser.getBootAddr()
|
||||
|
||||
#relative load
|
||||
loader = HexLoader(dongle, 0xe0, False, None, False)
|
||||
dongle = getDongle(args.apdu)
|
||||
|
||||
loader.validateTargetId(args.targetId)
|
||||
hash = loader.load(0xFF, 0xF0, parser.getAreas(), args.bootAddr)
|
||||
loader.run(parser.getAreas(), args.bootAddr)
|
||||
#relative load
|
||||
loader = HexLoader(dongle, 0xe0, False, None, False)
|
||||
|
||||
loader.validateTargetId(args.targetId)
|
||||
hash = loader.load(0xFF, 0xF0, parser, reverse=args.reverse, doCRC=(not args.nocrc))
|
||||
loader.run(args.bootAddr)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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'));
|
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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()
|
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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,65 +17,85 @@
|
|||
********************************************************************************
|
||||
"""
|
||||
|
||||
from .comm import getDongle
|
||||
from .deployed import getDeployedSecretV2
|
||||
from secp256k1 import PrivateKey
|
||||
from Crypto.Cipher import AES
|
||||
import argparse
|
||||
import sys
|
||||
import fileinput
|
||||
|
||||
def get_argparser():
|
||||
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):
|
||||
return int(x, 0)
|
||||
return int(x, 0)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--fileName", help="Set the file name to load")
|
||||
parser.add_argument("--apdu", help="Display APDU log", action='store_true')
|
||||
parser.add_argument("--scp", help="open secure channel to exchange apdu", action='store_true')
|
||||
parser.add_argument("--targetId", help="Set the chip target ID", type=auto_int)
|
||||
parser.add_argument("--rootPrivateKey", help="Set the root private key")
|
||||
if __name__ == '__main__':
|
||||
from .comm import getDongle
|
||||
from .deployed import getDeployedSecretV2
|
||||
from .ecWrapper import PrivateKey
|
||||
from Crypto.Cipher import AES
|
||||
import sys
|
||||
import fileinput
|
||||
import binascii
|
||||
|
||||
args = parser.parse_args()
|
||||
args = get_argparser().parse_args()
|
||||
|
||||
if args.targetId == None:
|
||||
args.targetId = 0x31100002
|
||||
if not args.fileName:
|
||||
#raise Exception("Missing fileName")
|
||||
file = sys.stdin
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
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))
|
||||
|
||||
for data in file:
|
||||
data = data.rstrip('\r\n').decode('hex')
|
||||
if args.scp:
|
||||
data = bytearray(data)
|
||||
if data[4] > 0 and len(data)>5:
|
||||
dongle.exchange(data[0:4] + scp.encryptAES(data[5:5+data[4]]) )
|
||||
else:
|
||||
dongle.exchange(data[0:5])
|
||||
if args.targetId is None:
|
||||
args.targetId = 0x31100002
|
||||
if not args.fileName:
|
||||
#raise Exception("Missing fileName")
|
||||
file = sys.stdin
|
||||
else:
|
||||
dongle.exchange(bytearray(data))
|
||||
file = open(args.fileName, "r")
|
||||
|
||||
|
||||
class SCP:
|
||||
|
||||
def __init__(self, dongle, targetId, rootPrivateKey):
|
||||
secret = getDeployedSecretV2(dongle, rootPrivateKey, targetId)
|
||||
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.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()
|
||||
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:
|
||||
dongle.exchange(bytearray(data))
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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,44 +17,56 @@
|
|||
********************************************************************************
|
||||
"""
|
||||
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexParser import IntelHexPrinter
|
||||
from secp256k1 import PrivateKey
|
||||
import hashlib
|
||||
import binascii
|
||||
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):
|
||||
return int(x, 0)
|
||||
return int(x, 0)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--hex", help="Hex file to be signed")
|
||||
parser.add_argument("--key", help="The private key to sign with (hex encoded)")
|
||||
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 ""
|
||||
|
||||
args = parser.parse_args()
|
||||
if __name__ == '__main__':
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexParser import IntelHexPrinter
|
||||
from .ecWrapper import PrivateKey
|
||||
import hashlib
|
||||
import sys
|
||||
import binascii
|
||||
|
||||
if args.hex == None:
|
||||
raise Exception("Missing hex filename to sign")
|
||||
if args.key == None:
|
||||
raise Exception("Missing private key")
|
||||
args = get_argparser().parse_args()
|
||||
|
||||
# parse
|
||||
parser = IntelHexParser(args.hex)
|
||||
if args.hex == None:
|
||||
raise Exception("Missing hex filename to sign")
|
||||
if args.key == None:
|
||||
raise Exception("Missing private key")
|
||||
|
||||
# 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()
|
||||
# parse
|
||||
parser = IntelHexParser(args.hex)
|
||||
|
||||
MASTER_PRIVATE = bytearray.fromhex(args.key)
|
||||
testMaster = PrivateKey(bytes(MASTER_PRIVATE))
|
||||
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
||||
# 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()
|
||||
|
||||
signature = testMaster.ecdsa_sign(bytes(dataToSign), raw=True)
|
||||
MASTER_PRIVATE = bytearray.fromhex(args.key)
|
||||
testMaster = PrivateKey(bytes(MASTER_PRIVATE))
|
||||
#testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
|
||||
|
||||
# test signature before printing it
|
||||
if testMaster.pubkey.ecdsa_verify(dataToSign, signature, raw=True):
|
||||
#print "Signer's public: " + binascii.hexlify(testMasterPublic)
|
||||
print testMaster.ecdsa_serialize(signature).encode('hex')
|
||||
signature = testMaster.ecdsa_sign(bytes(dataToSign), raw=True)
|
||||
|
||||
# test signature before printing it
|
||||
if testMaster.pubkey.ecdsa_verify(dataToSign, signature, raw=True):
|
||||
#print("Signer's public: " + binascii.hexlify(testMasterPublic))
|
||||
print(hexstr(testMaster.ecdsa_serialize(signature)))
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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,43 +17,48 @@
|
|||
********************************************************************************
|
||||
"""
|
||||
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexParser import IntelHexPrinter
|
||||
from secp256k1 import PublicKey
|
||||
import hashlib
|
||||
import binascii
|
||||
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):
|
||||
return int(x, 0)
|
||||
return int(x, 0)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--hex", help="Hex file to be verify")
|
||||
parser.add_argument("--key", help="The public key to verify with (hex encoded)")
|
||||
parser.add_argument("--signature", help="The signature to verify with (hex encoded)")
|
||||
if __name__ == '__main__':
|
||||
from .hexParser import IntelHexParser
|
||||
from .hexParser import IntelHexPrinter
|
||||
from .ecWrapper import PublicKey
|
||||
import hashlib
|
||||
import binascii
|
||||
|
||||
args = parser.parse_args()
|
||||
args = get_argparser().parse_args()
|
||||
|
||||
if args.hex == None:
|
||||
raise Exception("Missing hex filename to verify")
|
||||
if args.key == None:
|
||||
raise Exception("Missing public key")
|
||||
if args.signature == None:
|
||||
raise Exception("Missing signature")
|
||||
if args.hex == None:
|
||||
raise Exception("Missing hex filename to verify")
|
||||
if args.key == None:
|
||||
raise Exception("Missing public key")
|
||||
if args.signature == None:
|
||||
raise Exception("Missing signature")
|
||||
|
||||
# parse
|
||||
parser = IntelHexParser(args.hex)
|
||||
# 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()
|
||||
# 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()
|
||||
|
||||
publicKey = PublicKey(bytes(bytearray.fromhex(args.key)), raw=True)
|
||||
signature = publicKey.ecdsa_deserialize(bytes(bytearray.fromhex(args.signature)))
|
||||
if not publicKey.ecdsa_verify(bytes(dataToSign), signature, raw=True):
|
||||
raise Exception("Signature not verified")
|
||||
publicKey = PublicKey(bytes(bytearray.fromhex(args.key)), raw=True)
|
||||
signature = publicKey.ecdsa_deserialize(bytes(bytearray.fromhex(args.signature)))
|
||||
if not publicKey.ecdsa_verify(bytes(dataToSign), signature, raw=True):
|
||||
raise Exception("Signature not verified")
|
||||
|
||||
print "Signature verified"
|
||||
print("Signature verified")
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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")
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
*******************************************************************************
|
||||
* 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,19 +5,17 @@ from setuptools import setup, find_packages
|
|||
from os.path import dirname, join
|
||||
import os
|
||||
|
||||
os.environ['SECP_BUNDLED_EXPERIMENTAL'] = "1"
|
||||
|
||||
here = dirname(__file__)
|
||||
setup(
|
||||
name='ledgerblue',
|
||||
version='0.1.6',
|
||||
version='0.1.17',
|
||||
author='Ledger',
|
||||
author_email='hello@ledger.fr',
|
||||
description='Python library to communicate with Ledger Blue/Nano S',
|
||||
long_description=open(join(here, 'README.md')).read(),
|
||||
url='https://github.com/LedgerHQ/blue-loader-python',
|
||||
packages=find_packages(),
|
||||
install_requires=['hidapi>=0.7.99', 'secp256k1>=0.12.1', 'pycrypto>=2.6.1'],
|
||||
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'],
|
||||
extras_require = {
|
||||
'smartcard': [ 'python-pyscard>=1.6.12-4build1' ]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue