Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 102 additions & 96 deletions ledgerblue/checkGenuine.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import argparse


def get_argparser():
parser = argparse.ArgumentParser(description="""Use attestation to determine if the device is a genuine Ledger
device.""")
Expand All @@ -27,87 +28,92 @@ def get_argparser():
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
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:
return {
'ecdh_secret': secret,
'devicePublicKey': devicePublicKey
}
else:
return None


if __name__ == '__main__':
from .ecWrapper import PrivateKey, PublicKey
Expand All @@ -120,11 +126,11 @@ def getDeployedSecretV2(dongle, masterPrivate, targetId, issuerKey):

args = get_argparser().parse_args()

if args.targetId == None:
args.targetId = 0x31000002
if args.targetId is None:
args.targetId = 0x31000002

if args.issuerKey == None:
args.issuerKey = "0490f5c9d15a0134bb019d2afd0bf297149738459706e7ac5be4abc350a1f818057224fce12ec9a65de18ec34d6e8c24db927835ea1692b14c32e9836a75dad609"
if args.issuerKey is None:
args.issuerKey = "0490f5c9d15a0134bb019d2afd0bf297149738459706e7ac5be4abc350a1f818057224fce12ec9a65de18ec34d6e8c24db927835ea1692b14c32e9836a75dad609"

privateKey = PrivateKey()
publicKey = str(privateKey.pubkey.serialize(compressed=False))
Expand All @@ -138,21 +144,21 @@ def getDeployedSecretV2(dongle, masterPrivate, targetId, issuerKey):
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 secret is not 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")
Expand Down