22 Handles Perfect Forward Secrecy (PFS) ephemeral keys exchange and rotation for contacts.
33
44 Handles:
5- - Generates and rotates ephemeral (one-time use) ML-KEM-1024 keys and (medium-term) Classic McEliece keys.
5+ - Generates and rotates ephemeral (one-time use) ML-KEM-1024 and Classic McEliece keys.
66 - Uses per-contact hash chains to prevent replay attacks, and verifies authenticity using ML-DSA-87.
77 - Sends and receives signed ephemeral keys using long-term signing keys.
88 - Updates local account storage with new key material after successful exchange.
2424 CHACHA20POLY1305_NONCE_LEN ,
2525 CLASSIC_MCELIECE_8_NAME ,
2626 CLASSIC_MCELIECE_8_PK_LEN ,
27- CLASSIC_MCELIECE_8_ROTATE_AT ,
2827 KEYS_HASH_CHAIN_LEN
2928)
3029from core .trad_crypto import (
@@ -86,8 +85,7 @@ def send_new_ephemeral_keys(user_data: dict, user_data_lock: threading.Lock, con
8685 Generate, encrypt, and send fresh ephemeral keys to a contact.
8786
8887 - Maintains a per-contact hash chain for signing key material.
89- - Generates new Kyber1024 keys every call.
90- - Optionally rotates McEliece keys if rotation threshold is reached.
88+ - Generates new Kyber1024 and McEliece keys every call.
9189 - Signs all key material with the long-term signing key.
9290 - Sends to the server using an authenticated HTTP request.
9391 - If successful, stores new keys in `user_data["tmp"]` for later update.
@@ -112,8 +110,6 @@ def send_new_ephemeral_keys(user_data: dict, user_data_lock: threading.Lock, con
112110 session_headers = user_data_copied ["tmp" ]["session_headers" ]
113111
114112
115- rotation_counter = user_data_copied ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][CLASSIC_MCELIECE_8_NAME ]["rotation_counter" ]
116- rotate_at = user_data_copied ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][CLASSIC_MCELIECE_8_NAME ]["rotate_at" ]
117113
118114 lt_sign_private_key = user_data_copied ["contacts" ][contact_id ]["lt_sign_keys" ]["our_keys" ]["private_key" ]
119115
@@ -131,14 +127,11 @@ def send_new_ephemeral_keys(user_data: dict, user_data_lock: threading.Lock, con
131127
132128 # Generate new ML-KEM-1024 keys for us
133129 kyber_private_key , kyber_public_key = generate_kem_keys (ML_KEM_1024_NAME )
134- publickeys_hashchain = our_hash_chain + kyber_public_key
135130
136- rotate_mceliece = False
137- if (rotate_at == rotation_counter ) or (user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][CLASSIC_MCELIECE_8_NAME ]["private_key" ] is None ):
138- # Generate Classic McEliece 8192128f keys
139- mceliece_private_key , mceliece_public_key = generate_kem_keys (CLASSIC_MCELIECE_8_NAME )
140- publickeys_hashchain += mceliece_public_key
141- rotate_mceliece = True
131+ # Generate Classic McEliece 8192128 keys
132+ mceliece_private_key , mceliece_public_key = generate_kem_keys (CLASSIC_MCELIECE_8_NAME )
133+
134+ publickeys_hashchain = our_hash_chain + kyber_public_key + mceliece_public_key
142135
143136 # Sign them with our per-contact long-term private key
144137 publickeys_hashchain_signature = create_signature (ML_DSA_87_NAME , publickeys_hashchain , lt_sign_private_key )
@@ -174,13 +167,8 @@ def send_new_ephemeral_keys(user_data: dict, user_data_lock: threading.Lock, con
174167 user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["staged_keys" ][ML_KEM_1024_NAME ]["public_key" ] = kyber_public_key
175168
176169
177- if rotate_mceliece :
178- user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["staged_keys" ][CLASSIC_MCELIECE_8_NAME ]["private_key" ] = mceliece_private_key
179- user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["staged_keys" ][CLASSIC_MCELIECE_8_NAME ]["public_key" ] = mceliece_public_key
180-
181-
182- user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][CLASSIC_MCELIECE_8_NAME ]["rotation_counter" ] = 0
183- user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][CLASSIC_MCELIECE_8_NAME ]["rotate_at" ] = CLASSIC_MCELIECE_8_ROTATE_AT
170+ user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["staged_keys" ][CLASSIC_MCELIECE_8_NAME ]["private_key" ] = mceliece_private_key
171+ user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["staged_keys" ][CLASSIC_MCELIECE_8_NAME ]["public_key" ] = mceliece_public_key
184172
185173
186174
@@ -260,9 +248,7 @@ def pfs_data_handler(user_data: dict, user_data_lock: threading.Lock, user_data_
260248
261249
262250 if (
263- (len (pfs_plaintext ) < ML_KEM_1024_PK_LEN + ML_DSA_87_SIGN_LEN + KEYS_HASH_CHAIN_LEN )
264- or
265- len (pfs_plaintext ) > ML_KEM_1024_PK_LEN + ML_DSA_87_SIGN_LEN + CLASSIC_MCELIECE_8_PK_LEN + KEYS_HASH_CHAIN_LEN
251+ len (pfs_plaintext ) != ML_KEM_1024_PK_LEN + ML_DSA_87_SIGN_LEN + CLASSIC_MCELIECE_8_PK_LEN + KEYS_HASH_CHAIN_LEN
266252 ):
267253 logger .error ("Contact (%s) gave us a PFS request with malformed strand plaintext length (%d)" , contact_id , len (pfs_plaintext ))
268254 return
@@ -295,17 +281,12 @@ def pfs_data_handler(user_data: dict, user_data_lock: threading.Lock, user_data_
295281 logger .error ("Contact keys hash chain does not match our computed hash chain! Skipping this PFS message..." )
296282 return
297283
298- contact_kyber_public_key = contact_publickeys_hashchain [KEYS_HASH_CHAIN_LEN : ML_KEM_1024_PK_LEN + KEYS_HASH_CHAIN_LEN ]
299-
300- if len (contact_publickeys_hashchain ) == ML_KEM_1024_PK_LEN + CLASSIC_MCELIECE_8_PK_LEN + KEYS_HASH_CHAIN_LEN :
301- logger .info ("contact (%s) has rotated their Kyber and McEliece keys" , contact_id )
284+
285+ logger .info ("contact (%s) has rotated their Kyber and McEliece keys" , contact_id )
302286
303- contact_mceliece_public_key = contact_publickeys_hashchain [ML_KEM_1024_PK_LEN + KEYS_HASH_CHAIN_LEN :]
304- with user_data_lock :
305- user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["contact_public_keys" ][CLASSIC_MCELIECE_8_NAME ] = contact_mceliece_public_key
287+ contact_kyber_public_key = contact_publickeys_hashchain [KEYS_HASH_CHAIN_LEN : ML_KEM_1024_PK_LEN + KEYS_HASH_CHAIN_LEN ]
306288
307- elif len (contact_publickeys_hashchain ) == ML_KEM_1024_PK_LEN + KEYS_HASH_CHAIN_LEN :
308- logger .info ("contact (%s) has rotated their Kyber keys" , contact_id )
289+ contact_mceliece_public_key = contact_publickeys_hashchain [ML_KEM_1024_PK_LEN + KEYS_HASH_CHAIN_LEN :]
309290
310291
311292 logger .info ("We are acknowledging contact's new PFS keys" )
@@ -315,7 +296,8 @@ def pfs_data_handler(user_data: dict, user_data_lock: threading.Lock, user_data_
315296 with user_data_lock :
316297 user_data ["contacts" ][contact_id ]["lt_sign_keys" ]["contact_hash_chain" ] = contact_hash_chain
317298 user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["contact_public_keys" ][ML_KEM_1024_NAME ] = contact_kyber_public_key
318-
299+ user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["contact_public_keys" ][CLASSIC_MCELIECE_8_NAME ] = contact_mceliece_public_key
300+
319301 our_kyber_private_key = user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][ML_KEM_1024_NAME ]["private_key" ]
320302 our_mceliece_private_key = user_data ["contacts" ][contact_id ]["ephemeral_keys" ]["our_keys" ][CLASSIC_MCELIECE_8_NAME ]["private_key" ]
321303
0 commit comments