Skip to content

0.2.0-draft feedback #12

@lxfontes

Description

@lxfontes

The current interface takes care of the most basic client use case: wrapping a stream.
It doesn't address the goal of "Enabling mTLS" as there is no client certificate support and no CA chain verification.

The handshake mechanism doesn't address 3 key TLS use cases:

  • Retrieving information about the negotiated tunnel: Example: openssl s_client -showcerts -servername www.example.com -connect wasi.dev:443 . For clients, this means it is not possible to check the remote certificate usage, extensions, and provenance.
  • Handshaking as a server: Tightly related to the point above. Servers rely on extracting client subject/peer information as "client identity".
  • Passing a CA-chain, vital for mTLS as most mTLS CA's are private.

In the spirit of keeping the interface minimal and allowing TLS offload, I'd propose the following changes to cover more ground ( names are suggestions ):

ClientHandshake

Remove DOMAIN from constructor, which today is there to perform hostname checks.
Add server-verify(DOMAIN, option<ca-cert-chain-pem>) method. This will be called by clients that want to perform hostname checks. Not calling this method means "I don't want to verify the servername" ( famous skip-verify ). Not passing a ca-cert-chain-pem means "Use the system/host CA list".
An alternative would be keeping fields at the constructor but making them optional. option<DOMAIN> being none means to skip verification, and the same optional behaviour for ca-cert-chain-pem. It's a style choice.

Add peer-identity(client-cert-pem, client-cert-key-pem) method. This will be called by clients that want to present a client certificate to the remote endpoint. Not calling this method means "I don't have a client certificate".
Similar to DOMAIN, could be an optional field in the constructor.

Client-Connection

Rename it to Connection as it will serve both client & server.

Add peer-identity -> result<connection-information> method. This will return a record with the negotiated tunnel information & remote peer information.

The connection-information record should carry:

  • tls version
  • cipher
  • session-id
  • session-ticket
  • session-lifetime
  • remote-peer ( certificate-information )
  • verification-chain ( list of certificate-information records )

The certificate-information record should carry:

  • subject ( and the bits that compose the subject like CN, O, OU )
  • serial
  • fingerprint
  • usage
  • extensions
  • public key
  • subject-alternate-names
  • timestamps ( issued, valid-before, not-valid-after )

FutureClientStreams

Rename to FutureStreams as it will serve both client & server.

Personal preference would be for the FutureClientStreams::get to return simply a result<Connection,io-error> and have the input & output streams inside the Connection. That get return is quite long and ergonomics in languages like Go is not the greatest ( see wasi-http Go implementations for ex ).

ServerHandshake

Similar to ClientHandshake, but for servers.
The key difference is that we construct TLS state prior to accepting connections so that information can be computed & cached. This resource will be reused.

constructor(server-cert-pem, server-cert-key, option<ca-cert-chain-pem>) prepares the server handshake. Here we also have the possibility of passing a ca-chain. Similarly to ClientHandshake, passing a ca-chain could be its own method.

accept(this, input-stream,output-stream) -> result<Future> Here we pass in the plain client streams and get back a pollable that resolves when server handshake is complete.

With these, the pseudo code for client becomes

// skip-verify
let handshake =  ClientHandshake::new(tcp_input, tcp_output);
let client = handshake.finish().await?;
// remote information
let session_info = client.peer_identity();

// get the streams from the client
let (tls_input, tls_output) = client.streams();
// send data to server and read
...........


// hostname verification with host-provided CA certificates
let handshake =  ClientHandshake::new(tcp_input, tcp_output);
handshake.server_verify("wasi.dev", None);

// hostname verification with custom CA and no client certs
let ca_cert_chain = "----PEM-ENCODED--- ........ ----PEM----";
let handshake =  ClientHandshake::new(tcp_input, tcp_output);
handshake.server_verify("wasi.dev", Some(ca_cert_chain));

// client certificates without remote verification
let client_cert = "----PEM-ENCODED--- ........ ----PEM----";
let client_cert_key = "----PEM-ENCODED--- ........ ----PEM----";
let handshake =  ClientHandshake::new(tcp_input, tcp_output);
handshake.peer_identity(client_cert, client_cert_key);

// client certificates with remote verification ( mtls )
let ca_cert_chain = "----PEM-ENCODED--- ........ ----PEM----";
let client_cert = "----PEM-ENCODED--- ........ ----PEM----";
let client_cert_key = "----PEM-ENCODED--- ........ ----PEM----";
let handshake =  ClientHandshake::new(tcp_input, tcp_output);
handshake.server_verify("wasi.dev", Some(ca_cert_chain));
handshake.peer_identity(client_cert, client_cert_key);

And for server

// no client certificate check
let server_cert = "----PEM-ENCODED--- ........ ----PEM----";
let server_cert_key = "----PEM-ENCODED--- ........ ----PEM----";
let handshake =  ServerHandshake::new(server_cert, server_cert_key, None);

// listen for new connections, then for each connection
let (plain_input_stream, plain_output_stream) = accept_new_connection();
let client = handshake.accept(plain_input_stream, plain_output_stream).await?;
// remote information
let session_info = client.peer_identity();

// get the streams from the client
let (tls_input, tls_output) = client.streams();


// and for client certificate check (mtls)
let ca_cert_chain = "----PEM-ENCODED--- ........ ----PEM----";
let server_cert = "----PEM-ENCODED--- ........ ----PEM----";
let server_cert_key = "----PEM-ENCODED--- ........ ----PEM----";
let handshake =  ServerHandshake::new(server_cert, server_cert_key, Some(ca_cert_chain));

// listen for new connections, then for each connection
let (plain_input_stream, plain_output_stream) = accept_new_connection();
let client = handshake.accept(plain_input_stream, plain_output_stream).await?;
.....

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions