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!)
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.
First, you'll need to set up certs -- we'll look at getting certs for dev and for your actual server.
This will generate self-signed certs for local development using mkcert (as recommended by the Actix Web example).
sudo apt install mkcert
mkcert 127.0.0.1 localhost
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,
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot certonly --standalone
Please enter the domain name(s) you would like on your certificate (comma and/or space separated)
(Enter 'c' to cancel): yourverycoolwebsite.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Could not bind TCP port 80 because it is already in use by another process on
this system (such as a web server). Please stop the program in question and then
try again.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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!
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())
}
.bind_rustls(format!("{}:{}", args.bind_host.unwrap_or("0.0.0.0".to_string()), args.port), certs)?
And now we have SSL set up for our Actix Web application!
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.