def test_example_doc(): # Each client must generate a long-term identity key pair. # This should be stored somewhere safe and persistent. identity_key_pair = identity_key.IdentityKeyPair.generate() # Clients must generate prekeys. The example here is generating a # single prekey, but clients will generate many as they are one-time use # and consumed when a message from a new chat participant is sent. See issue #7. pre_key_pair = curve.KeyPair.generate() # Clients must generate a registration_id and store it somewhere safe and persistent. registration_id = 12 # TODO generate (not yet supported in upstream crate) # The InMemSignalProtocolStore is a single object which provide the four storage # interfaces required: IdentityKeyStore (for one's own identity key state and the (public) # identity keys for other chat participants), PreKeyStore (for one's own prekey state), # SignedPreKeyStore (for one's own signed prekeys), and SessionStore (for established sessions # with chat participants). store = storage.InMemSignalProtocolStore(identity_key_pair, registration_id) # Clients should also generate a signed prekey. signed_pre_key_pair = curve.KeyPair.generate() serialized_signed_pre_pub_key = signed_pre_key_pair.public_key().serialize( ) signed_pre_key_signature = (store.get_identity_key_pair().private_key( ).calculate_signature(serialized_signed_pre_pub_key)) # Clients should store their prekeys (both one-time and signed) in the protocol store along # with IDs that can be used to retrieve them later. pre_key_id = 10 pre_key_record = state.PreKeyRecord(pre_key_id, pre_key_pair) store.save_pre_key(pre_key_id, pre_key_record) signed_pre_key_id = 33 signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, 42, # This is a timestamp since the signed prekeys should be periodically rotated signed_pre_key_pair, signed_pre_key_signature, ) store.save_signed_pre_key(signed_pre_key_id, signed_prekey)
def create_pre_key_bundle(store): pre_key_pair = curve.KeyPair.generate() signed_pre_key_pair = curve.KeyPair.generate() signed_pre_key_public = signed_pre_key_pair.public_key().serialize() signed_pre_key_signature = ( store.get_identity_key_pair() .private_key() .calculate_signature(signed_pre_key_public) ) device_id = random.randint(1, 10000) pre_key_id = random.randint(1, 10000) signed_pre_key_id = random.randint(1, 10000) pre_key_bundle = state.PreKeyBundle( store.get_local_registration_id(), device_id, pre_key_id, pre_key_pair.public_key(), signed_pre_key_id, signed_pre_key_pair.public_key(), signed_pre_key_signature, store.get_identity_key_pair().identity_key(), ) store.save_pre_key(pre_key_id, state.PreKeyRecord(pre_key_id, pre_key_pair)) timestamp = random.randint(1, 10000) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, timestamp, signed_pre_key_pair, signed_pre_key_signature, ) store.save_signed_pre_key(signed_pre_key_id, signed_prekey) return pre_key_bundle
# Store should be persisted to disk store = storage.InMemSignalProtocolStore(identity_key_pair, registration_id) # Store this somewhere signed_pre_key_id = 233 signed_pre_key_pair = curve.KeyPair.generate() signed_pre_key_public = signed_pre_key_pair.public_key().serialize() signed_pre_key_signature = (store.get_identity_key_pair().private_key(). calculate_signature(signed_pre_key_public)) signed_prekey_timestamp = int(time.time()) # prekey = state.PreKeyRecord(pre_key_id, pre_key_pair) # store.save_pre_key(pre_key_id, prekey) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, signed_prekey_timestamp, signed_pre_key_pair, signed_pre_key_signature, ) store.save_signed_pre_key(signed_pre_key_id, signed_prekey) print("done! now attempting registration with the server...") resp = requests.post('http://127.0.0.1:8081/api/v2/register', data=json.dumps({ "signed_prekey_id": signed_pre_key_id, "signed_prekey": signed_pre_key_public.hex(), "signed_prekey_timestamp": signed_prekey_timestamp, "identity_key": identity_key_pair.public_key().serialize().hex(),
def test_optional_one_time_prekey(): alice_address = address.ProtocolAddress("+14151111111", DEVICE_ID) bob_address = address.ProtocolAddress("+14151111112", DEVICE_ID) alice_identity_key_pair = identity_key.IdentityKeyPair.generate() bob_identity_key_pair = identity_key.IdentityKeyPair.generate() alice_registration_id = 1 # TODO: generate these bob_registration_id = 2 alice_store = storage.InMemSignalProtocolStore(alice_identity_key_pair, alice_registration_id) bob_store = storage.InMemSignalProtocolStore(bob_identity_key_pair, bob_registration_id) bob_signed_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_public = bob_signed_pre_key_pair.public_key().serialize( ) bob_signed_pre_key_signature = (bob_store.get_identity_key_pair( ).private_key().calculate_signature(bob_signed_pre_key_public)) signed_pre_key_id = 22 bob_pre_key_bundle = state.PreKeyBundle( bob_store.get_local_registration_id(), DEVICE_ID, None, # No prekey None, # No prekey signed_pre_key_id, bob_signed_pre_key_pair.public_key(), bob_signed_pre_key_signature, bob_store.get_identity_key_pair().identity_key(), ) session.process_prekey_bundle( bob_address, alice_store, bob_pre_key_bundle, ) assert alice_store.load_session(bob_address).session_version() == 3 original_message = b"Hobgoblins hold themselves to high standards of military honor" outgoing_message = session_cipher.message_encrypt(alice_store, bob_address, original_message) outgoing_message.message_type() == 3 # 3 == CiphertextMessageType::PreKey incoming_message = protocol.PreKeySignalMessage.try_from( outgoing_message.serialize()) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, 42, bob_signed_pre_key_pair, bob_signed_pre_key_signature, ) bob_store.save_signed_pre_key(signed_pre_key_id, signed_prekey) plaintext = session_cipher.message_decrypt(bob_store, alice_address, incoming_message) assert original_message == plaintext
def test_bad_message_bundle(): alice_address = address.ProtocolAddress("+14151111111", DEVICE_ID) bob_address = address.ProtocolAddress("+14151111112", DEVICE_ID) alice_identity_key_pair = identity_key.IdentityKeyPair.generate() bob_identity_key_pair = identity_key.IdentityKeyPair.generate() alice_registration_id = 1 # TODO: generate these bob_registration_id = 2 alice_store = storage.InMemSignalProtocolStore(alice_identity_key_pair, alice_registration_id) bob_store = storage.InMemSignalProtocolStore(bob_identity_key_pair, bob_registration_id) bob_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_public = bob_signed_pre_key_pair.public_key().serialize( ) bob_signed_pre_key_signature = (bob_store.get_identity_key_pair( ).private_key().calculate_signature(bob_signed_pre_key_public)) pre_key_id = 31337 signed_pre_key_id = 22 bob_pre_key_bundle = state.PreKeyBundle( bob_store.get_local_registration_id(), DEVICE_ID, pre_key_id, bob_pre_key_pair.public_key(), signed_pre_key_id, bob_signed_pre_key_pair.public_key(), bob_signed_pre_key_signature, bob_store.get_identity_key_pair().identity_key(), ) session.process_prekey_bundle( bob_address, alice_store, bob_pre_key_bundle, ) bob_prekey = state.PreKeyRecord(pre_key_id, bob_pre_key_pair) bob_store.save_pre_key(pre_key_id, bob_prekey) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, 42, bob_signed_pre_key_pair, bob_signed_pre_key_signature, ) bob_store.save_signed_pre_key(signed_pre_key_id, signed_prekey) assert alice_store.load_session(bob_address) assert alice_store.load_session(bob_address).session_version() == 3 original_message = b"Hobgoblins hold themselves to high standards of military honor" assert bob_store.get_pre_key(pre_key_id) outgoing_message = session_cipher.message_encrypt(alice_store, bob_address, original_message) outgoing_message.message_type() == 3 # 3 == CiphertextMessageType::PreKey outgoing_message_wire = outgoing_message.serialize() edit_point = len(outgoing_message_wire) - 10 corrupted_message = (outgoing_message_wire[:edit_point] + bytes([outgoing_message_wire[edit_point] ^ 0x01]) + outgoing_message_wire[edit_point + 1:]) incoming_message = protocol.PreKeySignalMessage.try_from(corrupted_message) # This incoming message is corrupted, so we expect an exception to be raised with pytest.raises(SignalProtocolException): session_cipher.message_decrypt(bob_store, alice_address, incoming_message) assert bob_store.get_pre_key(pre_key_id) incoming_message = protocol.PreKeySignalMessage.try_from( outgoing_message_wire) plaintext = session_cipher.message_decrypt(bob_store, alice_address, incoming_message) assert original_message == plaintext # Trying to get the prekey will now fail, as the prekey has been used and removed from the store with pytest.raises(SignalProtocolException, match="invalid prekey identifier"): assert bob_store.get_pre_key(pre_key_id)
def test_repeat_bundle_message_v3(): alice_address = address.ProtocolAddress("+14151111111", DEVICE_ID) bob_address = address.ProtocolAddress("+14151111112", DEVICE_ID) alice_identity_key_pair = identity_key.IdentityKeyPair.generate() bob_identity_key_pair = identity_key.IdentityKeyPair.generate() alice_registration_id = 1 # TODO: generate these bob_registration_id = 2 alice_store = storage.InMemSignalProtocolStore(alice_identity_key_pair, alice_registration_id) bob_store = storage.InMemSignalProtocolStore(bob_identity_key_pair, bob_registration_id) bob_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_public = bob_signed_pre_key_pair.public_key().serialize( ) bob_signed_pre_key_signature = (bob_store.get_identity_key_pair( ).private_key().calculate_signature(bob_signed_pre_key_public)) pre_key_id = 31337 signed_pre_key_id = 22 bob_pre_key_bundle = state.PreKeyBundle( bob_store.get_local_registration_id(), DEVICE_ID, pre_key_id, bob_pre_key_pair.public_key(), signed_pre_key_id, bob_signed_pre_key_pair.public_key(), bob_signed_pre_key_signature, bob_store.get_identity_key_pair().identity_key(), ) session.process_prekey_bundle( bob_address, alice_store, bob_pre_key_bundle, ) assert alice_store.load_session(bob_address) assert alice_store.load_session(bob_address).session_version() == 3 original_message = b"Hobgoblins hold themselves to high standards of military honor" outgoing_message1 = session_cipher.message_encrypt(alice_store, bob_address, original_message) outgoing_message2 = session_cipher.message_encrypt(alice_store, bob_address, original_message) outgoing_message1.message_type() == 3 # 3 == CiphertextMessageType::PreKey outgoing_message2.message_type() == 3 # 3 == CiphertextMessageType::PreKey incoming_message = protocol.PreKeySignalMessage.try_from( outgoing_message1.serialize()) bob_prekey = state.PreKeyRecord(pre_key_id, bob_pre_key_pair) bob_store.save_pre_key(pre_key_id, bob_prekey) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, 42, bob_signed_pre_key_pair, bob_signed_pre_key_signature, ) bob_store.save_signed_pre_key(signed_pre_key_id, signed_prekey) ptext = session_cipher.message_decrypt(bob_store, alice_address, incoming_message) assert original_message == ptext bob_outgoing = session_cipher.message_encrypt(bob_store, alice_address, original_message) assert bob_outgoing.message_type( ) == 2 # 2 == CiphertextMessageType::Whisper alice_decrypts = session_cipher.message_decrypt(alice_store, bob_address, bob_outgoing) assert alice_decrypts == original_message # Verify the second message can be processed incoming_message2 = protocol.PreKeySignalMessage.try_from( outgoing_message2.serialize()) ptext = session_cipher.message_decrypt(bob_store, alice_address, incoming_message2) assert original_message == ptext bob_outgoing = session_cipher.message_encrypt(bob_store, alice_address, original_message) alice_decrypts = session_cipher.message_decrypt(alice_store, bob_address, bob_outgoing) assert alice_decrypts == original_message
def test_basic_prekey_v3(): alice_address = address.ProtocolAddress("+14151111111", DEVICE_ID) bob_address = address.ProtocolAddress("+14151111112", DEVICE_ID) alice_identity_key_pair = identity_key.IdentityKeyPair.generate() bob_identity_key_pair = identity_key.IdentityKeyPair.generate() alice_registration_id = 1 # TODO: generate these bob_registration_id = 2 alice_store = storage.InMemSignalProtocolStore(alice_identity_key_pair, alice_registration_id) bob_store = storage.InMemSignalProtocolStore(bob_identity_key_pair, bob_registration_id) bob_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_public = bob_signed_pre_key_pair.public_key().serialize( ) bob_signed_pre_key_signature = (bob_store.get_identity_key_pair( ).private_key().calculate_signature(bob_signed_pre_key_public)) pre_key_id = 31337 signed_pre_key_id = 22 bob_pre_key_bundle = state.PreKeyBundle( bob_store.get_local_registration_id(), DEVICE_ID, pre_key_id, bob_pre_key_pair.public_key(), signed_pre_key_id, bob_signed_pre_key_pair.public_key(), bob_signed_pre_key_signature, bob_store.get_identity_key_pair().identity_key(), ) assert alice_store.load_session(bob_address) is None # Below standalone function would make more sense as a method on alice_store? session.process_prekey_bundle( bob_address, alice_store, bob_pre_key_bundle, ) assert alice_store.load_session(bob_address) assert alice_store.load_session(bob_address).session_version() == 3 original_message = b"Hobgoblins hold themselves to high standards of military honor" outgoing_message = session_cipher.message_encrypt(alice_store, bob_address, original_message) outgoing_message.message_type() == 3 # 3 == CiphertextMessageType::PreKey outgoing_message_wire = outgoing_message.serialize() # Now over to fake Bob for processing the first message incoming_message = protocol.PreKeySignalMessage.try_from( outgoing_message_wire) bob_prekey = state.PreKeyRecord(pre_key_id, bob_pre_key_pair) bob_store.save_pre_key(pre_key_id, bob_prekey) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, 42, bob_signed_pre_key_pair, bob_signed_pre_key_signature, ) bob_store.save_signed_pre_key(signed_pre_key_id, signed_prekey) assert bob_store.load_session(alice_address) is None plaintext = session_cipher.message_decrypt(bob_store, alice_address, incoming_message) assert original_message == plaintext bobs_response = b"Who watches the watchers?" assert bob_store.load_session(alice_address) bobs_session_with_alice = bob_store.load_session(alice_address) assert bobs_session_with_alice.session_version() == 3 assert len(bobs_session_with_alice.alice_base_key()) == 32 + 1 bob_outgoing = session_cipher.message_encrypt(bob_store, alice_address, bobs_response) assert bob_outgoing.message_type( ) == 2 # 2 == CiphertextMessageType::Whisper # Now back to fake alice alice_decrypts = session_cipher.message_decrypt(alice_store, bob_address, bob_outgoing) assert alice_decrypts == bobs_response run_interaction(alice_store, alice_address, bob_store, bob_address) alice_identity_key_pair = identity_key.IdentityKeyPair.generate() alice_registration_id = 1 # TODO: generate these alice_store = storage.InMemSignalProtocolStore(alice_identity_key_pair, alice_registration_id) bob_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_public = bob_signed_pre_key_pair.public_key().serialize( ) bob_signed_pre_key_signature = (bob_store.get_identity_key_pair( ).private_key().calculate_signature(bob_signed_pre_key_public)) pre_key_id = 31337 signed_pre_key_id = 22 bob_pre_key_bundle = state.PreKeyBundle( bob_store.get_local_registration_id(), DEVICE_ID, pre_key_id + 1, bob_pre_key_pair.public_key(), signed_pre_key_id + 1, bob_signed_pre_key_pair.public_key(), bob_signed_pre_key_signature, bob_store.get_identity_key_pair().identity_key(), ) bob_prekey = state.PreKeyRecord(pre_key_id + 1, bob_pre_key_pair) bob_store.save_pre_key(pre_key_id + 1, bob_prekey) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id + 1, 42, bob_signed_pre_key_pair, bob_signed_pre_key_signature, ) bob_store.save_signed_pre_key(signed_pre_key_id + 1, signed_prekey) session.process_prekey_bundle( bob_address, alice_store, bob_pre_key_bundle, ) outgoing_message = session_cipher.message_encrypt(alice_store, bob_address, original_message) with pytest.raises(SignalProtocolException, match="untrusted identity"): session_cipher.message_decrypt(bob_store, alice_address, outgoing_message) assert bob_store.save_identity( alice_address, alice_store.get_identity_key_pair().identity_key()) decrypted = session_cipher.message_decrypt(bob_store, alice_address, outgoing_message) assert decrypted == original_message # Sign pre-key with wrong key bob_pre_key_bundle = state.PreKeyBundle( bob_store.get_local_registration_id(), DEVICE_ID, pre_key_id, bob_pre_key_pair.public_key(), signed_pre_key_id, bob_signed_pre_key_pair.public_key(), bob_signed_pre_key_signature, alice_store.get_identity_key_pair().identity_key(), ) with pytest.raises(SignalProtocolException): session.process_prekey_bundle(bob_address, alice_store, bob_pre_key_bundle)
def test_basic_large_message(): alice_address = address.ProtocolAddress("+14151111111", DEVICE_ID) bob_address = address.ProtocolAddress("+14151111112", DEVICE_ID) alice_identity_key_pair = identity_key.IdentityKeyPair.generate() bob_identity_key_pair = identity_key.IdentityKeyPair.generate() alice_registration_id = 1 # TODO: generate these bob_registration_id = 2 alice_store = storage.InMemSignalProtocolStore(alice_identity_key_pair, alice_registration_id) bob_store = storage.InMemSignalProtocolStore(bob_identity_key_pair, bob_registration_id) bob_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_pair = curve.KeyPair.generate() bob_signed_pre_key_public = bob_signed_pre_key_pair.public_key().serialize( ) bob_signed_pre_key_signature = (bob_store.get_identity_key_pair( ).private_key().calculate_signature(bob_signed_pre_key_public)) pre_key_id = 31337 signed_pre_key_id = 22 bob_pre_key_bundle = state.PreKeyBundle( bob_store.get_local_registration_id(), DEVICE_ID, pre_key_id, bob_pre_key_pair.public_key(), signed_pre_key_id, bob_signed_pre_key_pair.public_key(), bob_signed_pre_key_signature, bob_store.get_identity_key_pair().identity_key(), ) assert alice_store.load_session(bob_address) is None # Below standalone function would make more sense as a method on alice_store? session.process_prekey_bundle( bob_address, alice_store, bob_pre_key_bundle, ) assert alice_store.load_session(bob_address) assert alice_store.load_session(bob_address).session_version() == 3 original_message = bytes(1024 * 1000) # 1 MB empty attachment outgoing_message = session_cipher.message_encrypt(alice_store, bob_address, original_message) outgoing_message.message_type() == 3 # 3 == CiphertextMessageType::PreKey outgoing_message_wire = outgoing_message.serialize() incoming_message = protocol.PreKeySignalMessage.try_from( outgoing_message_wire) bob_prekey = state.PreKeyRecord(pre_key_id, bob_pre_key_pair) bob_store.save_pre_key(pre_key_id, bob_prekey) signed_prekey = state.SignedPreKeyRecord( signed_pre_key_id, 42, bob_signed_pre_key_pair, bob_signed_pre_key_signature, ) bob_store.save_signed_pre_key(signed_pre_key_id, signed_prekey) plaintext = session_cipher.message_decrypt(bob_store, alice_address, incoming_message) assert original_message == plaintext