Skip to content
This repository was archived by the owner on Mar 10, 2026. It is now read-only.

Commit c7dd401

Browse files
committed
wip: plausiable denitlbity
1 parent 5ae091b commit c7dd401

File tree

9 files changed

+152
-276
lines changed

9 files changed

+152
-276
lines changed

logic/background_worker.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from core.requests import http_request
22
from logic.storage import save_account_data
33
from logic.contacts import save_contact
4-
from logic.get_user import get_target_lt_public_key
54
from logic.smp import smp_unanswered_questions, smp_data_handler
65
from logic.pfs import pfs_data_handler
76
from logic.message import messages_data_handler

logic/contacts.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,21 @@ def generate_random_nickname(user_data: dict, user_data_lock, contact_id: str, n
2929
return nickname
3030

3131

32-
def save_contact(user_data: dict, user_data_lock, contact_id: str, contact_public_key: bytes) -> None:
32+
def save_contact(user_data: dict, user_data_lock, contact_id: str) -> None:
3333
with user_data_lock:
3434
if contact_id in user_data["contacts"]:
3535
raise ValueError("Contact already saved!")
3636

37-
for i, v in user_data["contacts"].items():
38-
if v["lt_sign_public_key"] == contact_public_key:
39-
raise ValueError("Contact long-term auth signing public-key is duplicated, and we have no idea why")
40-
4137

4238
user_data["contacts"][contact_id] = {
4339
"nickname": None,
44-
"lt_sign_public_key": contact_public_key,
40+
"lt_sign_keys": {
41+
"contact_public_key": None,
42+
"our_keys": {
43+
"private_key": None,
44+
"public_key": None
45+
}
46+
},
4547
"lt_sign_key_smp": {
4648
"verified": False,
4749
"pending_verification": False,
@@ -63,13 +65,6 @@ def save_contact(user_data: dict, user_data_lock, contact_id: str, contact_publi
6365
"contact_hash_chain": None
6466

6567
},
66-
"message_sign_keys": {
67-
"contact_public_key": None,
68-
"our_keys": {
69-
"private_key": None,
70-
"public_key": None
71-
}
72-
},
7368
"our_pads": {
7469
"replay_protection_number": None,
7570
"pads": None

logic/get_user.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
1-
from base64 import b64decode
21
from core.requests import http_request
32

4-
def get_target_lt_public_key(user_data: dict, target_id: str) -> bytes:
5-
url = user_data["server_url"]
3+
def check_if_contact_exists(user_data: dict, user_data_lock, contact_id: str) -> bool:
4+
with user_data_lock:
5+
url = user_data["server_url"]
66

77
try:
8-
response = http_request(f"{url}/get_user?user_id={target_id}", "GET")
8+
response = http_request(f"{url}/get_user?user_id={contact_id}", "GET")
99
except:
1010
raise ValueError("Could not connect to server, try again")
1111

1212
if not "status" in response:
13-
raise ValueError("Server gave a malformed response! This could be a malicious act, we suggest you retry and if problem persists use another server")
13+
raise ValueError("Server gave a malformed response")
1414

15-
if response["status"] == "failure" or not response.get("public_key"):
15+
if response["status"] == "failure":
1616
if not 'error' in response:
17-
raise ValueError("Server gave a malformed response! This could be a malicious act, we suggest you retry and if problem persists use another server")
17+
raise ValueError("Server gave a malformed response")
1818
else:
1919
raise ValueError(response["error"][:1024])
2020

2121
if response["status"] == "success":
22+
return True
2223

23-
# Dry run to validate server's base64. Helps prevents denial-of-service startup crashes -
24-
# when client first reads parses their account file
25-
try:
26-
return b64decode(response["public_key"], validate=True)
27-
except:
28-
raise ValueError("Server gave a malformed public_key! This could be a malicious act, we suggest you retry and if problem persists use another server")
24+
return False

logic/message.py

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from core.requests import http_request
22
from logic.storage import save_account_data
3-
from logic.pfs import send_new_ephemeral_keys, rotate_ephemeral_keys
3+
from logic.pfs import send_new_ephemeral_keys
44
from core.crypto import *
55
from core.constants import *
66
from base64 import b64decode, b64encode
@@ -74,10 +74,12 @@ def send_message_processor(user_data, user_data_lock, contact_id: str, message:
7474

7575

7676
contact_kyber_public_key = user_data["contacts"][contact_id]["ephemeral_keys"]["contact_public_key"]
77-
our_d5_private_key = user_data["contacts"][contact_id]["message_sign_keys"]["our_keys"]["private_key"]
77+
our_lt_private_key = user_data["contacts"][contact_id]["lt_sign_keys"]["our_keys"]["private_key"]
7878

7979

80-
if (not contact_kyber_public_key) or (not our_d5_private_key):
80+
81+
if (not contact_kyber_public_key):
82+
logger.debug("This shouldn't usually happen, contact kyber keys are not initialized even once yet???")
8183
ui_queue.put({
8284
"type": "showwarning",
8385
"title": f"Warning for {contact_id[:32]}",
@@ -88,41 +90,46 @@ def send_message_processor(user_data, user_data_lock, contact_id: str, message:
8890
return False
8991

9092

91-
with user_data_lock:
92-
our_pads_empty = bool(user_data["contacts"][contact_id]["our_pads"]["pads"])
9393

94+
with user_data_lock:
95+
our_pads = user_data["contacts"][contact_id]["our_pads"]["pads"]
9496

95-
# If we have keys, but not enough one-time-pads, we send new pads to the contact
96-
if not our_pads_empty:
97-
logger.debug("Not enough pads to send message")
97+
# If we have keys, but no one-time-pads, we send new pads to the contact
98+
if not our_pads:
99+
logger.debug("We have no pads to send message")
98100

99-
if not generate_and_send_pads(user_data, user_data_lock, contact_id, contact_kyber_public_key, our_d5_private_key, ui_queue):
100-
return False
101-
102-
103101
with user_data_lock:
104-
# Increment the rotation_counter
105-
user_data["contacts"][contact_id]["ephemeral_keys"]["rotation_counter"] += 1
106-
102+
107103
rotation_counter = user_data["contacts"][contact_id]["ephemeral_keys"]["rotation_counter"]
108104
rotate_at = user_data["contacts"][contact_id]["ephemeral_keys"]["rotate_at"]
109105

110-
111-
logger.debug("Incremented rotation_counter by 1. (%d)", rotation_counter)
112106

113-
# See if we should rotate our keys
107+
# We rotate keys before generating and sending new batch of pads because
108+
# ephemeral key exchanges always get processed before messages do.
109+
# Which means if we generate and send pads with contact's, we would be using his old key, which would get overriden by the request, even if we send pads first
110+
# This is because of our server archiecture which prioritizes PFS requests before messages.
111+
#
114112
if rotation_counter == rotate_at:
115113
logger.info("We are rotating our ephemeral keys for contact (%s)", contact_id)
116114
ui_queue.put({"type": "showinfo", "title": "Perfect Forward Secrecy", "message": f"We are rotating our ephemeral keys for contact ({contact_id[:32]})"})
117-
rotate_ephemeral_keys(user_data, user_data_lock, contact_id, ui_queue)
115+
send_new_ephemeral_keys(user_data, user_data_lock, contact_id, ui_queue)
118116

119117
save_account_data(user_data, user_data_lock)
120118
return False
119+
120+
if not generate_and_send_pads(user_data, user_data_lock, contact_id, contact_kyber_public_key, our_lt_private_key, ui_queue):
121+
return False
121122

123+
124+
with user_data_lock:
125+
our_pads = user_data["contacts"][contact_id]["our_pads"]["pads"]
126+
127+
user_data["contacts"][contact_id]["ephemeral_keys"]["rotation_counter"] += 1
128+
129+
logger.debug("Incremented rotation_counter by 1. (%d)", rotation_counter)
122130

123131

124132
with user_data_lock:
125-
our_pads = user_data["contacts"][contact_id]["our_pads"]["pads"]
126133
replay_protection_number = user_data["contacts"][contact_id]["our_pads"]["replay_protection_number"]
127134

128135
message_encoded = message.encode("utf-8")
@@ -173,7 +180,7 @@ def send_message_processor(user_data, user_data_lock, contact_id: str, message:
173180
"replay_protection_number": replay_protection_number
174181
})
175182

176-
json_inner_payload_signature = create_signature("Dilithium5", json_inner_payload.encode("utf-8"), our_d5_private_key)
183+
json_inner_payload_signature = create_signature("Dilithium5", json_inner_payload.encode("utf-8"), our_lt_private_key)
177184
json_inner_payload_signature = b64encode(json_inner_payload_signature).decode()
178185

179186
payload = {
@@ -208,9 +215,9 @@ def messages_data_handler(user_data, user_data_lock, user_data_copied, ui_queue,
208215
return
209216

210217

211-
contact_d5_public_key = user_data_copied["contacts"][contact_id]["message_sign_keys"]["contact_public_key"]
218+
contact_public_key = user_data_copied["contacts"][contact_id]["lt_sign_keys"]["contact_public_key"]
212219

213-
if not contact_d5_public_key:
220+
if not contact_public_key:
214221
logger.warning("Contact per-contact Dilithium5 public key is missing.. skipping message")
215222
return
216223

@@ -219,7 +226,7 @@ def messages_data_handler(user_data, user_data_lock, user_data_copied, ui_queue,
219226

220227
if message["msg_type"] == "new_otp_batch":
221228
payload_signature = b64decode(message["payload_signature"], validate=True)
222-
valid_signature = verify_signature("Dilithium5", message["json_payload"].encode("utf-8"), payload_signature, contact_d5_public_key)
229+
valid_signature = verify_signature("Dilithium5", message["json_payload"].encode("utf-8"), payload_signature, contact_public_key)
223230
if not valid_signature:
224231
logger.debug("Invalid OTP batch signature.. possible MiTM ?")
225232
return
@@ -247,7 +254,7 @@ def messages_data_handler(user_data, user_data_lock, user_data_copied, ui_queue,
247254

248255
elif message["msg_type"] == "new_message":
249256
payload_signature = b64decode(message["payload_signature"], validate=True)
250-
valid_signature = verify_signature("Dilithium5", message["json_payload"].encode("utf-8"), payload_signature, contact_d5_public_key)
257+
valid_signature = verify_signature("Dilithium5", message["json_payload"].encode("utf-8"), payload_signature, contact_public_key)
251258
if not valid_signature:
252259
logger.debug("Invalid new message signature.. possible MiTM ?")
253260
return

0 commit comments

Comments
 (0)