summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstale <redkugelblitzin@gmail.com>2022-05-03 20:22:43 -0300
committerstale <redkugelblitzin@gmail.com>2022-05-03 20:22:43 -0300
commit88cc92f64dfc4a241410f67b20dec4680461a344 (patch)
tree1fe938cc6013c8f8692881cd1803a27fac3a26a9
parent2f9687126ecb538f40b57bd6963129b406786175 (diff)
this is terrible
-rw-r--r--Cargo.lock201
-rw-r--r--Cargo.toml4
-rw-r--r--page.html3
-rw-r--r--src/main.rs92
-rw-r--r--src/tls_stuff.rs159
5 files changed, 425 insertions, 34 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cd78441..74011a6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -30,6 +30,12 @@ dependencies = [
]
[[package]]
+name = "bumpalo"
+version = "3.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
+
+[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -42,6 +48,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -267,6 +279,19 @@ dependencies = [
]
[[package]]
+name = "hyper-tungstenite"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0ea2c1b59596d6b1302fe616266257a58b079f68fee329d6d111f79241cb7fd"
+dependencies = [
+ "hyper",
+ "pin-project",
+ "tokio",
+ "tokio-tungstenite",
+ "tungstenite",
+]
+
+[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -294,6 +319,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
+name = "js-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -414,6 +448,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
+name = "pin-project"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -489,12 +543,58 @@ dependencies = [
]
[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rustls"
+version = "0.20.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921"
+dependencies = [
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
+dependencies = [
+ "base64",
+]
+
+[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
name = "sha-1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -537,6 +637,12 @@ dependencies = [
]
[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
name = "syn"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -614,6 +720,17 @@ dependencies = [
]
[[package]]
+name = "tokio-rustls"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
name = "tokio-tungstenite"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -730,6 +847,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
name = "url"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -776,6 +899,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
+name = "wasm-bindgen"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
+
+[[package]]
+name = "web-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
name = "websweeper"
version = "1.0.0"
dependencies = [
@@ -783,9 +980,11 @@ dependencies = [
"futures-channel",
"futures-util",
"hyper",
+ "hyper-tungstenite",
"rand",
+ "rustls-pemfile",
"tokio",
- "tokio-tungstenite",
+ "tokio-rustls",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 28dec21..9d4c83c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,9 @@ edition = "2018"
[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
-tokio-tungstenite = "*"
+tokio-rustls = "0.23"
+rustls-pemfile = "1"
+hyper-tungstenite = "0.8"
rand = "0.8"
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
futures-channel = "0.3"
diff --git a/page.html b/page.html
index 52cfb77..e2aec9b 100644
--- a/page.html
+++ b/page.html
@@ -45,7 +45,8 @@
</body>
<script>
window.id = NaN;
- let s = new WebSocket(`ws://${window.location.hostname}:31236`);
+ let wsproto = (window.location.protocol == "https:")? "wss:": "ws:";
+ let s = new WebSocket(`${wsproto}//${window.location.hostname}:${window.location.port}`);
let info_elem = document.getElementById("miscinfo");
let board_elem = document.getElementById("board");
let name = "deadbeef";
diff --git a/src/main.rs b/src/main.rs
index d42ad9f..1367c60 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,10 +7,12 @@ use std::{
};
mod minesweeper;
+mod tls_stuff;
use minesweeper::*;
-//use std::convert::{ TryFrom, TryInto };
+use tls_stuff::*;
use hyper::{ Method, StatusCode, Body, Request, Response, Server };
+use hyper::server::conn::{ AddrStream, AddrIncoming };
use hyper::service::{make_service_fn, service_fn};
use tokio::sync::{
RwLock,
@@ -21,9 +23,9 @@ type HtmlResult = Result<Response<Body>, Response<Body>>;
use futures_channel::mpsc::{unbounded, UnboundedSender};
use futures_util::{future, pin_mut, stream::TryStreamExt, StreamExt, sink::SinkExt};
-use tokio::net::{TcpListener, TcpStream};
use tokio::fs;
-use tokio_tungstenite::tungstenite::protocol::Message;
+use hyper_tungstenite::{ tungstenite::protocol::Message, HyperWebsocket };
+use tokio_rustls::{ rustls, Accept, server::TlsStream };
type Tx = UnboundedSender<Message>;
type MovReqTx = mpsc::UnboundedSender<MetaMove>;
@@ -41,23 +43,51 @@ const PAGE_RELPATH: &str = "./page.html";
const FONT_FILE_FUCKIT: &[u8] = include_bytes!("./VT323-Regular.ttf");
#[tokio::main]
-async fn main() {
+async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let sequential_id = Arc::new(AtomicUsize::new(0));
let peers = PeerMap::new(RwLock::new(HashMap::new()));
let peer_info: PeerInfo = (peers.clone(), sequential_id.clone());
- let http_addr = SocketAddr::from(([0, 0, 0, 0], 31235));
- println!("Http on {}", http_addr);
+ let addr = SocketAddr::from(([0, 0, 0, 0], 31235));
+
+ // Build TLS configuration.
+ let tls_cfg = {
+ // Load public certificate.
+ let certs = load_certs("cert.pem")?;
+ // Load private key.
+ let key = load_private_key("cert.rsa")?;
+ // Do not use client certificate authentication.
+ let mut cfg = rustls::ServerConfig::builder()
+ .with_safe_defaults()
+ .with_no_client_auth()
+ .with_single_cert(certs, key)
+ .map_err(|e| error(format!("{}", e)))?;
+ // Configure ALPN to accept HTTP/2, HTTP/1.1 in that order.
+ cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
+ Arc::new(cfg)
+ };
+
+ // Create a TCP listener via tokio.
+ let incoming = AddrIncoming::bind(&addr)?;
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
let game_t = tokio::spawn(gameloop(cmd_rx, peers.clone()));
- // need to await this one at some point
- let conn_l = tokio::spawn(conn_listener(peer_info.clone(), cmd_tx.clone()));
- let http_serv = make_service_fn(|_| {
+ let serv = make_service_fn(|socket: &tls_stuff::TlsStream| {
+ let addr = {
+ if let State::Streaming(ref s) = &socket.state {
+ s.get_ref().0.remote_addr()
+ } else {
+ std::net::SocketAddr::new(std::net::Ipv4Addr::new(0,0,0,0).into(),0)
+ }
+ };
+ let peer_info = peer_info.clone();
+ let cmd_tx = cmd_tx.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
+ let peer_info = peer_info.clone();
+ let cmd_tx = cmd_tx.clone();
async move {
- Ok::<_,Infallible>(match handle_http_req(req).await {
+ Ok::<_,Infallible>(match handle_req(req, peer_info.clone(), cmd_tx.clone(), addr).await {
Ok(r) => r,
Err(r) => r,
})
@@ -66,12 +96,15 @@ async fn main() {
}
});
- let server = Server::bind(&http_addr)
- .serve(http_serv)
+ // Run the future, keep going until an error occurs.
+ println!("Starting to serve on https://{}.", addr);
+ let server = Server::builder(TlsAcceptor::new(tls_cfg, incoming))
+ .serve(serv)
.with_graceful_shutdown(shutdown_signal());
if let Err(e) = server.await {
eprint!("server error: {}", e);
}
+ Ok(())
}
// If a move is made, broadcast new board, else just send current board
@@ -111,32 +144,17 @@ async fn gameloop(mut move_rx: mpsc::UnboundedReceiver<MetaMove>, peers: PeerMap
}
}
-async fn conn_listener(peer_info: PeerInfo, cmd_tx: MovReqTx) {
- let ws_addr = SocketAddr::from(([0, 0, 0, 0], 31236));
- let ws_socket = TcpListener::bind(&ws_addr).await;
- let ws_listener = ws_socket.expect("Failed to bind");
- // Let's spawn the handling of each connection in a separate task.
- println!("Websocket on {}", ws_addr);
- while let Ok((stream, addr)) = ws_listener.accept().await {
- tokio::spawn(peer_connection(peer_info.clone(), cmd_tx.clone(), stream, addr));
- }
-}
-
-async fn peer_connection(peer_info: PeerInfo, cmd_tx: MovReqTx, raw_stream: TcpStream, addr: SocketAddr) {
+async fn peer_connection(peer_info: PeerInfo, cmd_tx: MovReqTx, socket: HyperWebsocket, addr: SocketAddr) {
+ let socket = socket.await.unwrap(); // FIXME error handling
println!("Incoming TCP connection from: {}", addr);
- let ws_stream = tokio_tungstenite::accept_async(raw_stream)
- .await
- .expect("Error during the websocket handshake occurred");
- println!("WebSocket connection established: {}", addr);
-
let peer_map = peer_info.0;
let peer_seqid = peer_info.1.fetch_add(1, atomic::Ordering::AcqRel);
let mut peer_name = "unknown".to_string();
let (tx, rx) = unbounded();
- let (outgoing, mut incoming) = ws_stream.split();
+ let (outgoing, mut incoming) = socket.split();
let process_incoming = async {
while let Ok(cmd) = incoming.try_next().await {
@@ -249,7 +267,15 @@ async fn peer_connection(peer_info: PeerInfo, cmd_tx: MovReqTx, raw_stream: TcpS
peer_map.write().await.remove(&addr);
}
-async fn handle_http_req(request: Request<Body>) -> HtmlResult {
+async fn handle_req(mut request: Request<Body>, peer_info: PeerInfo, cmd_tx: MovReqTx, addr: SocketAddr) -> HtmlResult {
+ if hyper_tungstenite::is_upgrade_request(&request) {
+ let (resp, wsocket) = hyper_tungstenite::upgrade(&mut request, None).expect("couldn't upgrade to websocket");
+ tokio::spawn(async move {
+ peer_connection(peer_info.clone(), cmd_tx.clone(), wsocket, addr).await;
+ });
+ return Ok(resp);
+ }
+
let page = fs::read_to_string(PAGE_RELPATH).await.unwrap();
let mut uri_path = request.uri().path().split('/').skip(1);
let actual_path = uri_path.next();
@@ -289,6 +315,10 @@ fn errpage<T: Error>(e: T) -> Response<Body> {
.unwrap()
}
+fn error(err: String) -> std::io::Error {
+ std::io::Error::new(std::io::ErrorKind::Other, err)
+}
+
async fn shutdown_signal() {
tokio::signal::ctrl_c()
.await
diff --git a/src/tls_stuff.rs b/src/tls_stuff.rs
new file mode 100644
index 0000000..83c0489
--- /dev/null
+++ b/src/tls_stuff.rs
@@ -0,0 +1,159 @@
+//! Simple HTTPS echo service based on hyper-rustls
+//!
+//! First parameter is the mandatory port to use.
+//! Certificate and private key are hardcoded to sample files.
+//! hyper will automatically use HTTP/2 if a client starts talking HTTP/2,
+//! otherwise HTTP/1.1 will be used.
+use core::task::{Context, Poll};
+use futures_util::ready;
+use hyper::server::accept::Accept;
+use hyper::server::conn::{AddrIncoming, AddrStream};
+use std::future::Future;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::vec::Vec;
+use std::{fs, io};
+use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+use tokio_rustls::rustls::{self, ServerConfig};
+
+fn error(err: String) -> io::Error {
+ io::Error::new(io::ErrorKind::Other, err)
+}
+
+pub enum State {
+ Handshaking(tokio_rustls::Accept<AddrStream>),
+ Streaming(tokio_rustls::server::TlsStream<AddrStream>),
+}
+
+// tokio_rustls::server::TlsStream doesn't expose constructor methods,
+// so we have to TlsAcceptor::accept and handshake to have access to it
+// TlsStream implements AsyncRead/AsyncWrite handshaking tokio_rustls::Accept first
+pub struct TlsStream {
+ pub state: State,
+}
+
+impl TlsStream {
+ pub fn new(stream: AddrStream, config: Arc<ServerConfig>) -> TlsStream {
+ let accept = tokio_rustls::TlsAcceptor::from(config).accept(stream);
+ TlsStream {
+ state: State::Handshaking(accept),
+ }
+ }
+}
+
+impl AsyncRead for TlsStream {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &mut ReadBuf,
+ ) -> Poll<io::Result<()>> {
+ let pin = self.get_mut();
+ match pin.state {
+ State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
+ Ok(mut stream) => {
+ let result = Pin::new(&mut stream).poll_read(cx, buf);
+ pin.state = State::Streaming(stream);
+ result
+ }
+ Err(err) => Poll::Ready(Err(err)),
+ },
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_read(cx, buf),
+ }
+ }
+}
+
+impl AsyncWrite for TlsStream {
+ fn poll_write(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &[u8],
+ ) -> Poll<io::Result<usize>> {
+ let pin = self.get_mut();
+ match pin.state {
+ State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
+ Ok(mut stream) => {
+ let result = Pin::new(&mut stream).poll_write(cx, buf);
+ pin.state = State::Streaming(stream);
+ result
+ }
+ Err(err) => Poll::Ready(Err(err)),
+ },
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_write(cx, buf),
+ }
+ }
+
+ fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
+ match self.state {
+ State::Handshaking(_) => Poll::Ready(Ok(())),
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_flush(cx),
+ }
+ }
+
+ fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
+ match self.state {
+ State::Handshaking(_) => Poll::Ready(Ok(())),
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_shutdown(cx),
+ }
+ }
+}
+
+pub struct TlsAcceptor {
+ config: Arc<ServerConfig>,
+ incoming: AddrIncoming,
+}
+
+impl TlsAcceptor {
+ pub fn new(config: Arc<ServerConfig>, incoming: AddrIncoming) -> TlsAcceptor {
+ TlsAcceptor { config, incoming }
+ }
+}
+
+impl Accept for TlsAcceptor {
+ type Conn = TlsStream;
+ type Error = io::Error;
+
+ fn poll_accept(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ ) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
+ let pin = self.get_mut();
+ match ready!(Pin::new(&mut pin.incoming).poll_accept(cx)) {
+ Some(Ok(sock)) => Poll::Ready(Some(Ok(TlsStream::new(sock, pin.config.clone())))),
+ Some(Err(e)) => Poll::Ready(Some(Err(e))),
+ None => Poll::Ready(None),
+ }
+ }
+}
+
+// Load public certificate from file.
+pub fn load_certs(filename: &str) -> io::Result<Vec<rustls::Certificate>> {
+ // Open certificate file.
+ let certfile = fs::File::open(filename)
+ .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
+ let mut reader = io::BufReader::new(certfile);
+
+ // Load and return certificate.
+ let certs = rustls_pemfile::certs(&mut reader)
+ .map_err(|_| error("failed to load certificate".into()))?;
+ Ok(certs
+ .into_iter()
+ .map(rustls::Certificate)
+ .collect())
+}
+
+// Load private key from file.
+pub fn load_private_key(filename: &str) -> io::Result<rustls::PrivateKey> {
+ // Open keyfile.
+ let keyfile = fs::File::open(filename)
+ .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
+ let mut reader = io::BufReader::new(keyfile);
+
+ // Load and return a single private key.
+ let keys = rustls_pemfile::rsa_private_keys(&mut reader)
+ .map_err(|_| error("failed to load private key".into()))?;
+ if keys.len() != 1 {
+ return Err(error("expected a single private key".into()));
+ }
+
+ Ok(rustls::PrivateKey(keys[0].clone()))
+}