blackbeam / rust-mysql-simple

Mysql client library implemented in rust.
Apache License 2.0
666 stars 145 forks source link

[TLS] How to send client certificates AND keys? #348

Closed chusteven closed 1 year ago

chusteven commented 1 year ago

Hello! Apologies in advance if this has been answered elsewhere... but I would like to establish a MySQL connection using this crate that is analogous to the following mysql CLI invocation:

mysql -h HOST \
  -p PW \
  -u USER \
  -ssl-ca ../certs/ca.pem \
  -ssl-cert ../certs/client-cert.pem \
  -ssl-key ../certs/client-key.pem
  -ssl-mode VERIFY_CA

I believe that this has the client verify (via the /path/to/ca.pem file) that the server sent along the correct certs. But by passing along the --ssl-cert and --ssl-key files as well I am asking the server to verify the client. Ref: https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-cert

The closest I got was the https://docs.rs/mysql/latest/mysql/struct.ClientIdentity.html struct, but I was unsure how the client cert and key could be set using this struct?

So far I have something like below. Any help would be greatly appreciated and thanks so much!

fn establish_conn(db_cfg: &DatabaseConfig) -> Result<PooledConn> {
    // Also exists ../certs/client-cert.pem
    let identity = mysql::ClientIdentity::new(Path::new("../certs/client-key.pem"));
    let sslopts = mysql::SslOpts::default()
        .with_client_identity(Some(identity))
        .with_root_cert_path(Some(Path::new("../certs/ca.pem")));

    let opts = mysql::OptsBuilder::new()
        .ip_or_hostname(Some(db_cfg.host.as_str()))
        .user(Some(db_cfg.user.as_str()))
        .pass(Some(db_cfg.password.as_str()))
        .db_name(Some(db_cfg.db.as_str()))
        .ssl_opts(Some(sslopts));
    let pool = Pool::new(opts)?;
    let conn = pool.get_conn()?;
    Ok(conn)
}
blackbeam commented 1 year ago

ClientIdentity::new expects a path to pkcs12 container. It is quite easy to create one from pem cert and key using openssl (I believe keytool should also work).

chusteven commented 1 year ago

Thank you this helped! I'm on macOS and there were a couple things I had to do: 1/ Use the legacy option

openssl pkcs12 -export -legacy -in client-cert.pem \
        -inkey client-key.pem \
        -name "client-pkcs12" \
        -out client.p12

2/ Work around Apple-specific things 😵

chusteven commented 1 year ago

By the way, @blackbeam , would you be open to adding documentation somewhere for this workaround? I'd be happy to do so myself. I was thinking either:

Or both :)


Additionally, I do think it would be cool to add an API to support passing client certs and keys natively. But I need to do some research on other MySQL libraries, since what I'm used to on the CLI may simply be different than how most client libraries choose to implement TLS 😅


EDIT: Looks like at least Python and Go support key/pairs through various interfaces/APIs:

So lemme poke at it some!

blackbeam commented 1 year ago

By the way, @blackbeam , would you be open to adding documentation somewhere for this workaround? I'd be happy to do so myself. I was thinking either:

  • In the README here
  • On the ClientIdentity struct here

This API is for the native-tls backend (rustls uses separate pem files), so I'd prefer the second option.