Encrypt messages between nodes

Both sides of the chat now generate secrets based on their private key
and the other side's public key. They encrypt each message with a nonce
before sending it, and use the nonce in the decryption.

This _does_ mean that the responder has to specify the starter's name
now so that they can select the proper public key for encryption.
This commit is contained in:
Teknique 2023-08-02 23:15:27 -07:00
parent 04354d79c9
commit 469aefb873

View File

@ -10,7 +10,8 @@ import config
import veilid import veilid
QUIT = b"QUIT" QUIT = "QUIT"
NONCE_LENGTH = 24
async def noop_callback(*args, **kwargs): async def noop_callback(*args, **kwargs):
@ -23,6 +24,7 @@ async def chatter(
router: veilid.api.RoutingContext, router: veilid.api.RoutingContext,
crypto_system: veilid.CryptoSystem, crypto_system: veilid.CryptoSystem,
key: veilid.TypedKey, key: veilid.TypedKey,
secret: veilid.SharedSecret,
send_subkey: veilid.ValueSubkey, send_subkey: veilid.ValueSubkey,
recv_subkey: veilid.ValueSubkey, recv_subkey: veilid.ValueSubkey,
): ):
@ -30,9 +32,24 @@ async def chatter(
last_seq = -1 last_seq = -1
async def encrypt(cleartext: str) -> bytes:
"""Encrypt the message with the shared secret and a random nonce."""
nonce = await crypto_system.random_nonce()
encrypted = await crypto_system.crypt_no_auth(cleartext.encode(), nonce, secret)
return nonce.to_bytes() + encrypted
async def decrypt(payload: bytes) -> str:
"""Decrypt the payload with the shared secret and the payload's nonce."""
nonce = veilid.Nonce.from_bytes(payload[:NONCE_LENGTH])
encrypted = payload[NONCE_LENGTH:]
cleartext = await crypto_system.crypt_no_auth(encrypted, nonce, secret)
return cleartext.decode()
# Prime the pumps. Especially when starting the conversation, this # Prime the pumps. Especially when starting the conversation, this
# causes the DHT key to propagate to the network. # causes the DHT key to propagate to the network.
await router.set_dht_value(key, send_subkey, b"Hello from the world!") await router.set_dht_value(key, send_subkey, await encrypt("Hello from the world!"))
while True: while True:
try: try:
@ -40,11 +57,11 @@ async def chatter(
except EOFError: except EOFError:
# Cat got your tongue? Hang up. # Cat got your tongue? Hang up.
print("Closing the chat.") print("Closing the chat.")
await router.set_dht_value(key, send_subkey, QUIT) await router.set_dht_value(key, send_subkey, await encrypt(QUIT))
return return
# Write the input message to the DHT key. # Write the input message to the DHT key.
await router.set_dht_value(key, send_subkey, msg.encode()) await router.set_dht_value(key, send_subkey, await encrypt(msg))
# In the real world, don't do this. People may tease you for it. # In the real world, don't do this. People may tease you for it.
# This is meant to be easy to understand for demonstration # This is meant to be easy to understand for demonstration
@ -60,11 +77,12 @@ async def chatter(
if resp.seq == last_seq: if resp.seq == last_seq:
continue continue
if resp.data == QUIT: msg = await decrypt(resp.data)
if msg == QUIT:
print("Other end closed the chat.") print("Other end closed the chat.")
return return
print(f"RECV< {resp.data.decode()}") print(f"RECV< {msg}")
last_seq = resp.seq last_seq = resp.seq
break break
@ -85,7 +103,9 @@ async def start(host: str, port: int, name: str):
router = await (await conn.new_routing_context()).with_privacy() router = await (await conn.new_routing_context()).with_privacy()
crypto_system = await conn.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_VLD0) crypto_system = await conn.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_VLD0)
async with router, crypto_system: async with crypto_system, router:
secret = await crypto_system.cached_dh(their_key, my_keypair.secret())
record = await router.create_dht_record(veilid.DHTSchema.smpl(0, members)) record = await router.create_dht_record(veilid.DHTSchema.smpl(0, members))
print(f"New chat key: {record.key}") print(f"New chat key: {record.key}")
print("Give that to your friend!") print("Give that to your friend!")
@ -97,27 +117,30 @@ async def start(host: str, port: int, name: str):
try: try:
# Write to the 1st subkey and read from the 2nd. # Write to the 1st subkey and read from the 2nd.
await chatter(router, crypto_system, record.key, 0, 1) await chatter(router, crypto_system, record.key, secret, 0, 1)
finally: finally:
await router.close_dht_record(record.key) await router.close_dht_record(record.key)
await router.delete_dht_record(record.key) await router.delete_dht_record(record.key)
async def respond(host: str, port: int, key: str): async def respond(host: str, port: int, name: str, key: str):
"""Reply to a friend's chat.""" """Reply to a friend's chat."""
conn = await veilid.json_api_connect(host, port, noop_callback) conn = await veilid.json_api_connect(host, port, noop_callback)
keys = config.read_keys() keys = config.read_keys()
my_keypair = keys["self"] my_keypair = keys["self"]
their_key = keys["peers"][name]
router = await (await conn.new_routing_context()).with_privacy() router = await (await conn.new_routing_context()).with_privacy()
crypto_system = await conn.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_VLD0) crypto_system = await conn.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_VLD0)
async with router, crypto_system: async with crypto_system, router:
secret = await crypto_system.cached_dh(their_key, my_keypair.secret())
await router.open_dht_record(key, my_keypair) await router.open_dht_record(key, my_keypair)
# As the responder, we're writing to the 2nd subkey and reading from the 1st. # As the responder, we're writing to the 2nd subkey and reading from the 1st.
await chatter(router, crypto_system, key, 1, 0) await chatter(router, crypto_system, key, secret, 1, 0)
async def keygen(host: str, port: int): async def keygen(host: str, port: int):
@ -175,6 +198,7 @@ def handle_command_line(arglist: list[str]):
cmd_start.set_defaults(func=start) cmd_start.set_defaults(func=start)
cmd_respond = subparsers.add_parser("respond", help=respond.__doc__) cmd_respond = subparsers.add_parser("respond", help=respond.__doc__)
cmd_respond.add_argument("name", help="Your friend's name")
cmd_respond.add_argument("key", help="The chat's DHT key") cmd_respond.add_argument("key", help="The chat's DHT key")
cmd_respond.set_defaults(func=respond) cmd_respond.set_defaults(func=respond)