Continuing the development of this chat. In this post I will do the client side. So I'll implement a TLS client using Rustls. I've started from the minimal client example, then altered it to look like this:
use std::io::{Read, Write, stdout};
use std::net::TcpStream;
use std::sync::Arc;
use rustls::RootCertStore;
use rustls::pki_types::CertificateDer;
use rustls::pki_types::pem::PemObject;
pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;
pub type GenResult<T> = Result<T, BoxedError>;
fn main() -> GenResult<()> {
let mut args = std::env::args();
args.next();
let root_cert_file = args.next().expect("missing root cert file argument.");
let server_name = args.next().expect("missing server name.");
let server_address = args.next().expect("missing server address.");
let root_cert: CertificateDer = CertificateDer::from_pem_file(root_cert_file)?;
let mut root_store = RootCertStore::empty();
root_store.add(root_cert)?;
let config = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
let server_name = server_name.try_into()?;
let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name)?;
let mut sock = TcpStream::connect(server_address)?;
let mut tls = rustls::Stream::new(&mut conn, &mut sock);
tls.write_all("Hello world!".as_bytes())?;
tls.conn.send_close_notify();
let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap();
writeln!(
&mut std::io::stderr(),
"Current ciphersuite: {:?}",
ciphersuite.suite()
)?;
let mut plaintext = Vec::new();
tls.read_to_end(&mut plaintext)?;
stdout().write_all(&plaintext)?;
Ok(())
}
The program takes 3 arguments: the root CA of the server, the server name, and the server address which contains the host:port pair. The CA cert is used to identify the server. The server name is used to match the name in the certificate. The server address is used for connection.
So in the previous post, I've made the keys and certificates. Alice has the Alice.crt for the root cert, Alice1.crt for the device cert, then it has the Alice.pem and Alice1.pem private keys for it. The device cert "Alice1" has the subject alternative name "alice". So I ran the server and the client with the following parameters:
./server certs/Alice1.crt certs/Alice.crt certs/Alice1.pem
./client certs/Alice.crt alice 127.0.0.1:4443
And it worked fine.
One modification I added to the server is to make it send the close notification, otherwise the client reading fails when the other side closes the connection.
I've committed this and the repository (https://git.jxzqj.com/chat2026.git) is now at the commit 3131d07526318080e8aeeee035edb25b2aa1d08a.
Normally the server name matches the DNS name that was used to resolve the IP address. But it can be anything else. This allows the very same IP and port to host multiple servers, possible different kinds of server if the appropriate proxy is in front of them.
In the next post I'll try to change the server and client to make them mutually authenticate.
See the latest posts below, click the "..." to see them all. Click the tags to filter by tag. You can also subscribe to RSS in those lists.
Double entry bookkeeping explained - english finance
Chat2026 part 10: continuing application design - english chat2026-devblog
If you want privacy, please use a desktop PC - english privacy
YouTube is now practically unsearchable - english rants
Chat2026 part 9: using CRL in the server and client examples - english chat2026-devblog