add personalization script
This commit is contained in:
parent
c6ecebb057
commit
8d42005e13
|
@ -139,4 +139,6 @@ so that the current key path matches m/1/1 and only then will the EXPORT KEY com
|
||||||
5. If you test your client against our fork of jCardSim instead of a real card, keep in mind that it supports unassisted
|
5. If you test your client against our fork of jCardSim instead of a real card, keep in mind that it supports unassisted
|
||||||
key derivation, but you shouldn't use it because, as explained above, it wouldn't work on the card.
|
key derivation, but you shouldn't use it because, as explained above, it wouldn't work on the card.
|
||||||
6. If using jCardSim, only use our fork, since some of the needed algorithms are unsupported in the upstream version.
|
6. If using jCardSim, only use our fork, since some of the needed algorithms are unsupported in the upstream version.
|
||||||
|
7. The pairing code is a randomly generated password (using whatever password generation algorithm is desired). This
|
||||||
|
password must be converted to a 256-bit key using PBKDF2 with the salt "Status Hardware Wallet Lite" and 50000 iterations.
|
||||||
|
|
||||||
|
|
11
README.md
11
README.md
|
@ -55,6 +55,17 @@ im.status.gradle.gpshell.kvn=0
|
||||||
im.status.wallet.test.simulated=false
|
im.status.wallet.test.simulated=false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Alternative installation method
|
||||||
|
This method does not require the JavaCard SDK but requires an already compiled CAP file. The cards generated this way
|
||||||
|
have a random PUK and pairing code so they have better security. However applet installation/removal is not disabled,
|
||||||
|
because the script is still meant to be used during the development phase.
|
||||||
|
|
||||||
|
1. Install GPShell and Python 3
|
||||||
|
2. Put the wallet.cap file in the same directory as status_hw_perso.py
|
||||||
|
3. Disconnect all card reader terminals from the system, except the one with the card where you want to install the applet
|
||||||
|
4. Run the status_hw_perso.py script with no arguments.
|
||||||
|
5. Take note of the pairing code and PUK output by the script
|
||||||
|
|
||||||
## Implementation notes
|
## Implementation notes
|
||||||
|
|
||||||
* The applet requires JavaCard 3.0.4 or later.
|
* The applet requires JavaCard 3.0.4 or later.
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import secrets
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
gpshell_template = """
|
||||||
|
mode_211
|
||||||
|
enable_trace
|
||||||
|
establish_context
|
||||||
|
card_connect
|
||||||
|
select -AID A000000151000000
|
||||||
|
open_sc -security 1 -keyind 0 -keyver 0 -mac_key 404142434445464748494a4b4c4d4e4f -enc_key 404142434445464748494a4b4c4d4e4f -kek_key 404142434445464748494a4b4c4d4e4f
|
||||||
|
send_apdu_nostop -sc 1 -APDU 80E400800E4F0C53746174757357616C6C6574
|
||||||
|
install_for_load -pkgAID 53746174757357616C6C6574
|
||||||
|
load -file wallet.cap
|
||||||
|
send_apdu -sc 1 -APDU 80E60C005F0C53746174757357616C6C65740F53746174757357616C6C65744170700F53746174757357616C6C657441707001002EC92C{:s}{:s}00
|
||||||
|
card_disconnect
|
||||||
|
release_context
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pbkdf2(digestmod, password: 'bytes', salt, count, dk_length) -> 'bytes':
|
||||||
|
def pbkdf2_function(pw, salt, count, i):
|
||||||
|
# in the first iteration, the hmac message is the salt
|
||||||
|
# concatinated with the block number in the form of \x00\x00\x00\x01
|
||||||
|
r = u = hmac.new(pw, salt + struct.pack(">i", i), digestmod).digest()
|
||||||
|
for i in range(2, count + 1):
|
||||||
|
# in subsequent iterations, the hmac message is the
|
||||||
|
# previous hmac digest. The key is always the users password
|
||||||
|
# see the hmac specification for notes on padding and stretching
|
||||||
|
u = hmac.new(pw, u, digestmod).digest()
|
||||||
|
# this is the exclusive or of the two byte-strings
|
||||||
|
r = bytes(i ^ j for i, j in zip(r, u))
|
||||||
|
return r
|
||||||
|
dk, h_length = b'', digestmod().digest_size
|
||||||
|
# we generate as many blocks as are required to
|
||||||
|
# concatinate to the desired key size:
|
||||||
|
blocks = (dk_length // h_length) + (1 if dk_length % h_length else 0)
|
||||||
|
for i in range(1, blocks + 1):
|
||||||
|
dk += pbkdf2_function(password, salt, count, i)
|
||||||
|
# The length of the key wil be dk_length to the nearest
|
||||||
|
# hash block size, i.e. larger than or equal to it. We
|
||||||
|
# slice it to the desired length befor returning it.
|
||||||
|
return dk[:dk_length]
|
||||||
|
|
||||||
|
def run():
|
||||||
|
puk = '{:012d}'.format(secrets.randbelow(999999999999))
|
||||||
|
pairing = secrets.token_urlsafe(12)
|
||||||
|
pairing_key = pbkdf2(hashlib.sha256, pairing.encode('utf-8'), 'Status Hardware Wallet Lite'.encode('utf-8'), 50000, 32).hex()
|
||||||
|
perso_script = gpshell_template.format(puk.encode('utf-8').hex(), pairing_key)
|
||||||
|
subprocess.run("gpshell", shell=True, check=True, input=perso_script.encode('utf-8'))
|
||||||
|
|
||||||
|
print('\n**************************************\nPairing password: {:s}\nPUK: {:s}'.format(pairing, puk))
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
Loading…
Reference in New Issue