Open petre-c opened 2 weeks ago
This error message usually appears when the static public key of the pin server does not match the oracle public key configured on the Jade. Are you certain that you have not regenerated the private.key
and public.key
after configuring the blind oracle on the Jade?
This error message usually appears when the static public key of the pin server does not match the oracle public key configured on the Jade. Are you certain that you have not regenerated the
private.key
andpublic.key
after configuring the blind oracle on the Jade?
Yes, I am certain that I have not regenerated the private.key
and public.key
.
I have attached them for your reference server_keys.zip
Oracle QR step:
Step 2/4 (video): https://github.com/user-attachments/assets/fdb41ea3-0b2a-492d-a24e-d83aa4c70a33
Which firmware version is your Jade on? Mine is on 1.0.24. I have tried with your keys and I cannot reproduce your problem.
I have just scanned the QR codes from the video you provided and it works on firmware version 1.0.24
I will try to downgrade to 1.0.24.
Will you please look at if 1.0.32 is complaining for you as well (and fix it, if so) or are you avoiding the firmware upgrade for some reason?
Will you please look at if 1.0.32 is complaining for you as well (and fix it, if so) or are you avoiding the firmware upgrade for some reason?
I am looking into it
Ok I see what's going on. In firmware version 1.0.28 the pinserver oracle protocol was changed to have only 2 steps.
Changed pinserver oracle protocol to only require a single roundtrip, exchanging a single base64-encoded string
My implementation was made for the old 4 step protocol.
Ok I see what's going on. In firmware version 1.0.28 the pinserver oracle protocol was changed to have only 2 steps.
Changed pinserver oracle protocol to only require a single roundtrip, exchanging a single base64-encoded string
My implementation was made for the old 4 step protocol.
And you will update your code to reflect the change, is that correct reasoning on my part?
From my testing I can confirm that 1.0.27 is the last version that works with SimpleJadePinServer.
And you will update your code to reflect the change, is that correct reasoning on my part?
I will look into it. It may take some time though, as I have a few other tasks on my plate at the moment.
Okay, I will wait patiently 🙂
@Filiprogrammer , have you had a chance to look into this?
Not yet, I am somewhat busy with my job and my studies. I might tackle this on the weekend.
I investigated the new protocol and written pseudocode for its implementation.
What is sent from the Jade to the pin server:
{
"id": "qrauth",
"result": {
"http_request": {
"params": {
"urls": [
"http://127.0.0.1:4443/set_pin",
""
],
"method": "POST",
"accept": "json",
"data": {
"data": "<base64 encoded (cke + replay counter + encrypted payload)>"
}
},
"on-reply": "pin"
}
}
}
What is sent from the pin server to the Jade:
{
"data": "<base64 encoded (encrypted AES key with HMAC appended)>"
}
set_pin
data = b64decode(data)
assert len(data) > 37
cke = data[:33]
replay_counter = data[33:37]
encrypted_data = data[37:]
private_key, public_key = generate_ec_key_pair(replay_counter, cke)
tweak = wally.sha256(wally.hmac_sha256(cke, replay_counter))
private_key = wally.ec_private_key_bip341_tweak(STATIC_SERVER_PRIVATE_KEY, tweak, 0)
wally.ec_private_key_verify(private_key)
public_key = wally.ec_public_key_from_private_key(private_key)
payload = wally.aes_cbc_with_ecdh_key(private_key, None, encrypted_data, cke, b'blind_oracle_request', wally.AES_FLAG_DECRYPT)
# set_pin requires client-passed entropy
assert len(payload) == wally.SHA256_LEN + wally.SHA256_LEN + wally.EC_SIGNATURE_RECOVERABLE_LEN
pin_secret = payload[:wally.SHA256_LEN]
entropy = payload[wally.SHA256_LEN: wally.SHA256_LEN + wally.SHA256_LEN]
sig = payload[wally.SHA256_LEN + wally.SHA256_LEN:]
signed_msg = wally.sha256(cke + replay_counter + pin_secret + entropy)
pin_pubkey = wally.ec_sig_to_public_key(signed_msg, sig)
pin_pubkey_hash = bytes(wally.sha256(pin_pubkey))
replay_local = None
try:
_, _, _, replay_local = load_pin_fields(pin_pubkey_hash, pin_pubkey)
# Enforce anti replay (client counter must be greater than the server counter)
client_counter = int.from_bytes(replay_counter, byteorder='little', signed=False)
server_counter = int.from_bytes(replay_local, byteorder='little', signed=False)
assert client_counter > server_counter
except FileNotFoundError:
pass
new_key = wally.hmac_sha256(os.urandom(32), entropy)
hash_pin_secret = wally.sha256(pin_secret)
replay_bytes = b'\x00\x00\x00\x00'
save_pin_fields(pin_pubkey_hash, hash_pin_secret, new_key, pin_pubkey, 0, replay_bytes)
aes_key = wally.hmac_sha256(new_key, pin_secret)
assert len(aes_key) == wally.AES_KEY_LEN_256
iv = os.urandom(wally.AES_BLOCK_LEN)
encrypted_key = wally.aes_cbc_with_ecdh_key(private_key, iv, aes_key, cke, b'blind_oracle_response', wally.AES_FLAG_ENCRYPT)
assert len(encrypted_key) == wally.AES_KEY_LEN_256 + (2*wally.AES_BLOCK_LEN) + wally.HMAC_SHA256_LEN
self.wfile.write(b'{"data":"' + b64encode(encrypted_key) + b'"}')
get_pin
data = b64decode(data)
assert len(data) > 37
cke = data[:33]
replay_counter = data[33:37]
encrypted_data = data[37:]
private_key, public_key = generate_ec_key_pair(replay_counter, cke)
tweak = wally.sha256(wally.hmac_sha256(cke, replay_counter))
private_key = wally.ec_private_key_bip341_tweak(STATIC_SERVER_PRIVATE_KEY, tweak, 0)
wally.ec_private_key_verify(private_key)
public_key = wally.ec_public_key_from_private_key(private_key)
payload = wally.aes_cbc_with_ecdh_key(private_key, None, encrypted_data, cke, b'blind_oracle_request', wally.AES_FLAG_DECRYPT)
# get_pin does not need client-passed entropy
assert len(payload) == wally.SHA256_LEN + wally.EC_SIGNATURE_RECOVERABLE_LEN
pin_secret = payload[:wally.SHA256_LEN]
sig = payload[wally.SHA256_LEN:]
signed_msg = wally.sha256(cke + replay_counter + pin_secret)
pin_pubkey = wally.ec_sig_to_public_key(signed_msg, sig)
pin_pubkey_hash = bytes(wally.sha256(pin_pubkey))
try:
saved_hash_pin_secret, saved_key, counter, replay_local = load_pin_fields(pin_pubkey_hash, pin_pubkey)
# Enforce anti replay (client counter must be greater than the server counter)
client_counter = int.from_bytes(replay_counter, byteorder='little', signed=False)
server_counter = int.from_bytes(replay_local, byteorder='little', signed=False)
assert client_counter > server_counter
hash_pin_secret = wally.sha256(pin_secret)
if hmac.compare_digest(saved_hash_pin_secret, hash_pin_secret):
print("Correct pin on the " + str(counter + 1) + ". attempt")
# Zero the 'bad guess counter' and update the replay_counter
save_pin_fields(pin_pubkey_hash, saved_hash_pin_secret, saved_key, pin_pubkey, 0, replay_counter)
else:
print("Wrong pin (" + str(counter + 1) + ". attempt)")
if counter >= 2:
os.remove(pins_path + "/" + bytes2hex(pin_pubkey_hash) + ".pin")
print("Too many wrong attempts")
else:
save_pin_fields(pin_pubkey_hash, saved_hash_pin_secret, saved_key, pin_pubkey, counter + 1, replay_counter)
# return junk key
saved_key = os.urandom(wally.AES_KEY_LEN_256)
except Exception:
# return junk key
saved_key = os.urandom(wally.AES_KEY_LEN_256)
aes_key = wally.hmac_sha256(saved_key, pin_secret)
assert len(aes_key) == wally.AES_KEY_LEN_256
iv = os.urandom(wally.AES_BLOCK_LEN)
encrypted_key = wally.aes_cbc_with_ecdh_key(private_key, iv, aes_key, cke, b'blind_oracle_response', wally.AES_FLAG_ENCRYPT)
assert len(encrypted_key) == wally.AES_KEY_LEN_256 + (2*wally.AES_BLOCK_LEN) + wally.HMAC_SHA256_LEN
self.wfile.write(b'{"data":"' + b64encode(encrypted_key) + b'"}')
Hey @Filiprogrammer , I saw you implemented setting a blind oracle with QR, well done and thank you!
I gave it a try - Oracle QR gets scanned successfully. After that, Jade produces a 'Network or server error' on Step 2/4 start_handshake:
I tried it on a debian distro and with docker.
My Jade is on firmware version 1.0.31.
Log from the terminal: