CloudRelayDialer.kt

2.7 KB · raw

1package com.peachpasswords.sync23import org.json.JSONObject45/**6 * One relayed reconnect session where WE speak first — the mirror of7 * [runCloudRelayResponderSession], and the half Android never had.8 *9 * Until this existed the phone could only ever wait, which was fine against a10 * browser extension (it can only dial) and a deadlock against another phone:11 * two waiters in the same room, nobody opening. See [weDialPeer] for how the12 * role is assigned.13 *14 * The wire is the extension's, byte for byte: an unencrypted `PEACH_RECONNECT`15 * selector naming the room, then Noise KK, then a padded PAIR_IDENTITY each16 * way, then the sync exchange.17 */18internal suspend fun runCloudRelayInitiatorSession(19    socket: RelayBinarySocket,20    known: PairedDevice,21    rendezvousId: String,22    ourStaticPrivateKey: ByteArray,23    ourStaticPublicKey: ByteArray,24    localIdentity: LocalChirpIdentity,25    vaultFactory: () -> ChirpSyncVaultAdapter?,26    onSyncComplete: () -> Unit,27) {28    val responderKeyHex = known.longTermPublicKeyHex29        ?: throw IllegalArgumentException("known peer missing static key")30    val responderKey = hexToPubkey(responderKeyHex)3132    // The capability names the room only; it grants no trust. KK proves both33    // private keys immediately after, and a wrong room is refused there.34    socket.send(35        JSONObject()36            .put("type", "PEACH_RECONNECT")37            .put("rendezvous_id", rendezvousId)38            .toString()39            .toByteArray(Charsets.UTF_8),40    )41    val session = noiseKKInitiator(42        ourStaticPrivateKey,43        ourStaticPublicKey,44        responderKey,45        socket::send,46        socket::receive,47    )48    try {49        socket.send(session.encrypt(SyncPadding.pad(encodePairIdentityFrame(localIdentity))))50        val theirIdentity = parsePairIdentityFrame(51            SyncPadding.unpad(session.decrypt(socket.receive())),52        )53        require(identityMatchesAuthenticatedPeer(known, theirIdentity, responderKey)) {54            "peer identity does not match authenticated KK key"55        }5657        val vault = vaultFactory() ?: return58        vault.bindAuthenticatedPeer(known)59        val outcome = runChirpSyncInitiator(60            ChirpSyncSession(61                remoteStaticPublicKey = responderKey.copyOf(),62                send = { plaintext -> socket.send(session.encrypt(SyncPadding.pad(plaintext))) },63                receive = { SyncPadding.unpad(session.decrypt(socket.receive())) },64                close = { socket.close() },65            ),66            vault,67        )68        if (outcome.status == SyncOutcomeStatus.COMPLETED) {69            onSyncComplete()70        }71    } finally {72        session.close()73    }74}