How To Set Up SSL with Actix Web and RustLS

I have implemented multithreaded server in C and C++ to varying levels of success over the years, but always stopped short of trying to get SSL working. Using Actix Web and RustLS though, it's surprisingly easy to get everything working. (I'm writing this mostly to make sure I don't forget in the future.)

In fact, you can just look at the official example for all you need! But it might be helpful to combine their example with the EFF Certbot guide. (This assumes you're using Ubuntu or PopOS.)

A word of warning: This is absolutely not the guide to follow if you're running a "real" website! I have no background in security, and it wouldn't surprise me if this is completely broken. (If you do happen to find something completely broken, please let me know!)

But why though?

I assume that most people would accept the need for signed certs for interactive websites -- but why in the world would you care about it for a site serving static content? Maybe you don't really need to, but it should help ensure that you're not being exposed to a man-in-the-middle attack. Also, I just think it's kinda cool and I wanted to figure out how to do it. Some day, I might even do more than serve static content too, so now it's all ready to go.

Getting Certs Ready

First, you'll need to set up certs -- we'll look at getting certs for dev and for your actual server.

Dev Certs

This will generate self-signed certs for local development using mkcert (as recommended by the Actix Web example).

  1. Install mkcert
  2. sudo apt install mkcert

  3. Generate certs for localhost dev
Prod Certs

This is the process to generate Let's Encrypt SSL certs using certbot for your actual server. I assume it's okay to temporarily take your server offline to do this -- if not,

  1. Install certbot
  2. Generate certs with certbot
  3. If the above command worked, you should see something like:
  4. Successfully received certificate.
    Certificate is saved at: /etc/letsencrypt/live/yourvercoolwebsite.com/fullchain.pem
    Key is saved at: /etc/letsencrypt/live/yourvercoolwebsite.com/privkey.pem
    This certificate expires on 2022-08-02.
    These files will be updated when the certificate renews.
    Certbot has set up a scheduled task to automatically renew this certificate in the background.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    If you like Certbot, please consider supporting our work by:
    * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
    * Donating to EFF: https://eff.org/donate-le
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  5. Be sure to make note of both files listed above! We'll need them for running rustls.

Using your certs with Actix-Web and RustLS

To make switching between dev and prod environments relatively painless, I'm going to recommend using Clap to parse command line arguments so you can dynamically set the host/port to bind to as well as the locations of the cert and key files. A better solution would probably be to have some config files that the application could read at start up -- a topic for a follow-up article!

  1. First, you'll need to ensure that you have the necessary dependencies in your cargo.toml file.
  2. Next, you'll need to open the files. The rustls-pemfile functions that are used to open the files take shared ref dyns to the BufReader trait, so if we follow the Actix Web example, we can do something like this:
  3. fn load_certs(args: &Args) -> Result<ServerConfig, String> {
    let cert_file = &mut BufReader::new(File::open(&args.cert).map_err(|e| e.to_string())?);
    let key_file = &mut BufReader::new(File::open(&args.key).map_err(|e| e.to_string())?);

    let cert_chain = certs(cert_file)
    .map_err(|e| e.to_string())?
    .into_iter()
    .map(Certificate)
    .collect();
    let mut keys: Vec<PrivateKey> = pkcs8_private_keys(key_file)
    .map_err(|e| e.to_string())?
    .into_iter()
    .map(PrivateKey)
    .collect();

    if keys.is_empty() {
    return Err("Could not locate PKCS 8 private keys.".to_string());
    }

    let config = ServerConfig::builder().with_safe_defaults().with_no_client_auth();
    config.with_single_cert(cert_chain, keys.remove(0)).map_err(|e| e.to_string())
    }

  4. Then, in the main function, we just need to replace bind with bind_rustls, which takes the host and port as a string plus the certs returned from load_certs.

And now we have SSL set up for our Actix Web application!

Screenshot of a Firefox URL bar with the address 'https://prestonfrom.com' and the SSL lock symbol

Thanks for reading! See the main page if you have questions or comments.

-------------------------------------------------------------------

This article was brought to you by 'Now or Never' played on repeat.