tlsnotary / tlsn

Rust implementation of the TLSNotary protocol
https://tlsnotary.org
266 stars 69 forks source link

Investigate switching back to upstream `rustls` #421

Open sinui0 opened 8 months ago

sinui0 commented 8 months ago

Newer releases of rustls have introduced abstraction layers for implementing custom cryptography backends for the client. Right now we have our own heavily modified fork which does the same and adds async internals.

We should really investigate whether we can ditch our fork and switch back to upstream now that this abstraction layer exists. Can we implement our own CryptoProvider? Or do we still need finer control over the client? Can we avoid needing async by running the blocking client on a dedicated thread/web worker?

The benefits of not having to maintain our own client and test suite are numerous.

themighty1 commented 3 months ago

Seems like CryptoProvider does indeed expose enough details for our TLSNotary protocol.

For reference here's an example implementation of CryptoProvider https://github.com/rustls/rustls/blob/7d998cd7efb6ef9d4350aa397d974db72b7e9ef5/provider-example/src/lib.rs#L21

pub fn provider() -> CryptoProvider {
    CryptoProvider {
        cipher_suites: ALL_CIPHER_SUITES.to_vec(),
        kx_groups: kx::ALL_KX_GROUPS.to_vec(),
        signature_verification_algorithms: verify::ALGORITHMS,
        secure_random: &Provider,
        key_provider: &Provider,
    }
}

Here's how the needed TLS details can be exposed to the TLSNotary protocol.

1. Exposing server ephemeral public key for key exchange.

For kx_groups we can implement crypto::SupportedKxGroup whose start method returns impl ActiveKeyExchange whose complete method exposes peer_pub_key which is the server ephemeral public key.

With peer_pub_key exposed, we can run MPC key exchange. The complete method will return a dummy SharedSecret (recall that our MPC TLS client doesn't know the full shared secret, so we need to return a dummy value)

2. Exposing client_random, server_random, and server signature over the key exchange params.

For signature_verification_algorithms's WebPkiSupportedAlgorithms::mapping we can implement SignatureVerificationAlgorithm whose verify_signature method will be invoked by rustls to check the server signature over the key exchange params.

fn verify_signature(
        &self,
        public_key: &[u8],
        message: &[u8],
        signature: &[u8],
    ) -> Result<(), InvalidSignature>;

Here message will be a concatenation of client_random+server_random+peer_pub_key( from Step 1).

3. Instantiating AEAD encryptor/decryptor.

For cipher_suites's field aead_alg we can implement Tls12AeadAlgorithm so that its encrypter method returns our custom MPC AEAD encrypter which implements MessageEncrypter.

We will ignore the encrypter args key and iv (since those will be dummy keys derived from a dummy SharedSecret in Step 1). Our MPC AEAD encrypter will use its own key references from the MPC VM.

(The above approach also applies to AEAD decrypter).

4. A workaround to compute verify_data (consult https://tls12.xargs.org/#client-handshake-finished)

Since rustls has a dummy MasterSecret derived from SharedSecret (from Step 1), it will be unable to compute the correct verify_data for the Client Finished message. (recall that in our MPC TLS the MasterSecret is secret-shared and is not known to any party).

Luckily, the CryptoProvider trait allows us to "intercept" the call to the PRF when verify_data is being computed and to return the correct verify_data computed in MPC.

To accomplish this, for cipher_suites's field prf_provider we can implement crypto::tls12::Prf whose method

fn for_secret(&self, output: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]);

allows us to check if label == client_finished and then to return the correct verify_data computed in MPC.

(The above approach also applies to computing verify_data for Server Finished).

cijimenez commented 1 month ago

hi! @sinui0 @themighty1 what is the details about this issue? I am part of the PSE 2024 core program