PairingAuthTest.kt

3.8 KB · raw

1package com.peachpasswords.sync23import kotlinx.coroutines.async4import kotlinx.coroutines.channels.Channel5import kotlinx.coroutines.runBlocking6import org.bouncycastle.crypto.params.X25519PrivateKeyParameters7import org.junit.Assert.assertArrayEquals8import org.junit.Assert.assertEquals9import org.junit.Test10import java.security.SecureRandom1112class PairingAuthTest {13    private data class Keys(val privateKey: ByteArray, val publicKey: ByteArray)1415    private fun keys(): Keys {16        val privateKey = X25519PrivateKeyParameters(SecureRandom())17        return Keys(privateKey.encoded, privateKey.generatePublicKey().encoded)18    }1920    private fun session(hash: ByteArray) = object : NoiseNKSession {21        override fun encrypt(plaintext: ByteArray): ByteArray = plaintext22        override fun decrypt(ciphertext: ByteArray): ByteArray = ciphertext23        override val handshakeHash: ByteArray = hash24    }2526    @Test27    fun `auth proof matches browser cross-platform vector`() {28        val proof = computeInitiatorAuthProof(29            initiatorPrivateKey = ByteArray(32) { 0x01 },30            initiatorPublicKey = hexToPubkey(31                "a4e09292b651c278b9772c569f5fa9bb13d906b46ab68c9df9dc2b4409f8a209",32            ),33            responderPublicKey = hexToPubkey(34                "ce8d3ad1ccb633ec7b70c17814a5c76ecd029685050d344745ba05870e587d59",35            ),36            nonce = ByteArray(32) { 0x03 },37            handshakeHash = ByteArray(32) { 0x04 },38        )3940        assertEquals(41            "d947d11a82892a65fdde94da5bbe6ca6034cf1f7e3268fc3ce5851b55f5813ce",42            pubkeyToHex(proof),43        )44    }4546    @Test47    fun `initiator proof round trips and returns authenticated key`() = runBlocking {48        val initiator = keys()49        val responder = keys()50        val hash = ByteArray(32) { 0x07 }51        val initToResponder = Channel<ByteArray>(Channel.UNLIMITED)52        val responderToInit = Channel<ByteArray>(Channel.UNLIMITED)5354        val prove = async {55            performInitiatorPairingAuth(56                session(hash),57                initiator.privateKey,58                initiator.publicKey,59                responder.publicKey,60                { initToResponder.send(it) },61                { responderToInit.receive() },62            )63        }64        val verify = async {65            performResponderPairingAuth(66                session(hash),67                responder.privateKey,68                responder.publicKey,69                { responderToInit.send(it) },70                { initToResponder.receive() },71            )72        }7374        prove.await()75        assertArrayEquals(initiator.publicKey, verify.await())76    }7778    @Test79    fun `proof from a different handshake hash is rejected`() = runBlocking {80        val initiator = keys()81        val responder = keys()82        val initToResponder = Channel<ByteArray>(Channel.UNLIMITED)83        val responderToInit = Channel<ByteArray>(Channel.UNLIMITED)8485        val prove = async {86            performInitiatorPairingAuth(87                session(ByteArray(32) { 0x07 }),88                initiator.privateKey,89                initiator.publicKey,90                responder.publicKey,91                { initToResponder.send(it) },92                { responderToInit.receive() },93            )94        }95        val verify = async {96            runCatching {97                performResponderPairingAuth(98                    session(ByteArray(32) { 0x09 }),99                    responder.privateKey,100                    responder.publicKey,101                    { responderToInit.send(it) },102                    { initToResponder.receive() },103                )104            }105        }106107        prove.await()108        val failure = verify.await().exceptionOrNull()109        assertEquals("auth proof did not verify", failure?.message)110    }111}