Skip to content

Commit

Permalink
Nano S 1.4 support, cleanup Python 3 support
Browse files Browse the repository at this point in the history
  • Loading branch information
BTChip github committed Mar 6, 2018
1 parent 9914b37 commit e834117
Show file tree
Hide file tree
Showing 16 changed files with 1,133 additions and 214 deletions.
55 changes: 55 additions & 0 deletions ledgerblue/Dongle.py
Original file line number Diff line number Diff line change
@@ -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
48 changes: 37 additions & 11 deletions ledgerblue/checkGenuine.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ def get_argparser():
def auto_int(x):
return int(x, 0)

def getDeployedSecretV2(dongle, masterPrivate, targetid, issuerKey):
def getDeployedSecretV2(dongle, masterPrivate, targetId, issuerKey):
testMaster = PrivateKey(bytes(masterPrivate))
testMasterPublic = bytearray(testMaster.pubkey.serialize(compressed=False))
targetid = bytearray(struct.pack('>I', targetid))
targetid = bytearray(struct.pack('>I', targetId))

# identify
apdu = bytearray([0xe0, 0x04, 0x00, 0x00]) + bytearray([len(targetid)]) + targetid
Expand Down Expand Up @@ -70,6 +70,7 @@ def getDeployedSecretV2(dongle, masterPrivate, targetid, issuerKey):
# 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')))
Expand All @@ -86,6 +87,7 @@ def getDeployedSecretV2(dongle, masterPrivate, targetid, issuerKey):
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
Expand All @@ -99,7 +101,13 @@ def getDeployedSecretV2(dongle, masterPrivate, targetid, issuerKey):
# Commit device ECDH channel
dongle.exchange(bytearray.fromhex('E053000000'))
secret = last_pub_key.ecdh(binascii.unhexlify(ephemeralPrivate.serialize()))
return secret[0:16]
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
Expand All @@ -123,20 +131,38 @@ def getDeployedSecretV2(dongle, masterPrivate, targetid, issuerKey):
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:
loader = HexLoader(dongle, 0xe0, True, secret)
data = b'\xFF'
data = loader.encryptAES(data)
try:
loader.exchange(loader.cla, 0x00, 0x00, 0x00, data)
except CommException as e:
genuine = (e.sw == 0x6D00)

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")
print ("Product is NOT genuine")
94 changes: 49 additions & 45 deletions ledgerblue/comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,49 +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

TIMEOUT=20000

try:
from smartcard.Exceptions import NoCardException
from smartcard.System import readers
from smartcard.util import toHexString, toBytes
SCARD = True
except ImportError:
SCARD = False

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"]

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 close(self):
pass

def setWaitImpl(self, waitImpl):
self.waitImpl = waitImpl
# 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):

Expand All @@ -74,8 +63,12 @@ def __init__(self, device, ledger=False, debug=False):
self.opened = True

def exchange(self, apdu, timeout=TIMEOUT):
if APDUGEN:
print("%s" % hexstr(apdu))
return

if self.debug:
print("=> %s" % hexstr(apdu))
print("HID => %s" % hexstr(apdu))
if self.ledger:
apdu = wrapCommandAPDU(0x0101, apdu, 64)
padSize = len(apdu) % 64
Expand All @@ -86,7 +79,8 @@ def exchange(self, apdu, timeout=TIMEOUT):
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
Expand Down Expand Up @@ -125,7 +119,7 @@ def exchange(self, apdu, timeout=TIMEOUT):
sw = (result[swOffset] << 8) + result[swOffset + 1]
response = result[dataStart : dataLength + dataStart]
if self.debug:
print("<= %s%.2x" % (hexstr(response), sw))
print("HID <= %s%.2x" % (hexstr(response), sw))
if sw != 0x9000:
raise CommException("Invalid status %04x" % sw, sw, response)
return response
Expand All @@ -141,6 +135,9 @@ def waitFirstResponse(self, timeout):
time.sleep(0.0001)
return bytearray(data)

def apduMaxDataSize(self):
return 255

def close(self):
if self.opened:
try:
Expand All @@ -159,11 +156,11 @@ def __init__(self, device, debug=False):

def exchange(self, apdu, timeout=TIMEOUT):
if self.debug:
print("=> %s" % hexstr(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" % (hexstr(response).replace(" ", ""), sw))
print("SC <= %s%.2x" % (hexstr(response).replace(" ", ""), sw))
if sw != 0x9000:
raise CommException("Invalid status %04x" % sw, sw, bytearray(response))
return bytearray(response)
Expand All @@ -177,18 +174,25 @@ def close(self):
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:
if hidDevice['vendor_id'] == 0x2c97 and ('interface_number' not in hidDevice or hidDevice['interface_number'] == 0):
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:
Expand Down

0 comments on commit e834117

Please sign in to comment.