Skip to content

Commit

Permalink
Add a test echo server and utils
Browse files Browse the repository at this point in the history
  • Loading branch information
MOZGIII committed Feb 2, 2024
1 parent 2aba431 commit 6754d6b
Show file tree
Hide file tree
Showing 28 changed files with 1,068 additions and 55 deletions.
313 changes: 313 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions crates/xwt-cert-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "xwt-cert-utils"
version = "0.1.0"
edition = "2021"
resolver = "2"
license = "MIT"
description = """
Useful utils for working with certificates, provided by and for xwt.
Not directly required to use xwt, but can be helpful. Also usable without xwt.
Inteded to work with both wasm and native.
"""
repository = "https://github.com/MOZGIII/xwt"

[dependencies]
base64 = "0.21"
pem = "3"
rcgen = { version = "0.11", optional = true }
ring = "0.16"
time = "0.3"

[features]
default = ["rcgen"]
6 changes: 6 additions & 0 deletions crates/xwt-cert-utils/src/digest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Compute a SHA256 digest for the given data.
///
/// Pass DER certificate here to compute its digest.
pub fn sha256(data: &[u8]) -> ring::digest::Digest {
ring::digest::digest(&ring::digest::SHA256, data)
}
25 changes: 25 additions & 0 deletions crates/xwt-cert-utils/src/fingerprint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/// RFC7469 fingerprint.
pub fn rfc7469(cert: &rcgen::Certificate) -> String {
use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};

let digest = crate::digest::sha256(&cert.get_key_pair().public_key_der());
Base64Engine.encode(digest)
}

/// SHA256 fingerprint.
///
/// Note for someone who wants to verify the output, it should be similar to
/// the output for the following command:
/// ```shell
/// openssl x509 -noout -fingerprint -sha256 -in certificate.pem
/// ```
pub fn sha256(cert_der: &[u8]) -> String {
let digest = crate::digest::sha256(cert_der);
let digest: &[u8] = digest.as_ref();

digest
.iter()
.map(|byte| format!("{:X}", byte))
.collect::<Vec<_>>()
.join(":")
}
52 changes: 52 additions & 0 deletions crates/xwt-cert-utils/src/gen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! Certificate generation facilities.
pub struct Params<'a> {
pub common_name: &'a str,
pub subject_alt_names: &'a [&'a str],
pub valid_days_before: u32,
pub valid_days_after: u32,
}

#[cfg(feature = "rcgen")]
impl<'a> Params<'a> {
pub fn into_rcgen_params(
self,
key_alg: &'static rcgen::SignatureAlgorithm,
key_pair: rcgen::KeyPair,
) -> rcgen::CertificateParams {
let mut dname = rcgen::DistinguishedName::new();
dname.push(rcgen::DnType::CommonName, self.common_name);

let now = time::OffsetDateTime::now_utc();

let mut cert_params = rcgen::CertificateParams::default();

cert_params
.distinguished_name
.push(rcgen::DnType::CommonName, self.common_name);
cert_params
.subject_alt_names
.extend(self.subject_alt_names.iter().map(|&s| match s.parse() {
Ok(ip) => rcgen::SanType::IpAddress(ip),
Err(_) => rcgen::SanType::DnsName(s.to_owned()),
}));
cert_params.alg = key_alg;
cert_params.key_pair = Some(key_pair);
cert_params.not_before = now
.checked_sub(time::Duration::days(self.valid_days_before.into()))
.unwrap();
cert_params.not_after = now
.checked_add(time::Duration::days(self.valid_days_after.into()))
.unwrap();

cert_params
}

pub fn into_rcgen_cert(
self,
key_alg: &'static rcgen::SignatureAlgorithm,
key_pair: rcgen::KeyPair,
) -> Result<rcgen::Certificate, rcgen::RcgenError> {
rcgen::Certificate::from_params(self.into_rcgen_params(key_alg, key_pair))
}
}
6 changes: 6 additions & 0 deletions crates/xwt-cert-utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Certificate utils and tooling.
pub mod digest;
pub mod fingerprint;
pub mod gen;
pub mod pem;
22 changes: 22 additions & 0 deletions crates/xwt-cert-utils/src/pem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
pub use base64::engine::general_purpose::STANDARD as Base64Engine;
pub use rcgen::RcgenError;

/// A type that represents a PEM-encoded public key.
pub struct PrivateKey(pub String);

/// A type that represents a PEM-encoded certificate.
pub struct Certificate(pub String);

/// Parse the certificate in PEM format.
pub fn parse(data: &str) -> Result<Vec<u8>, pem::PemError> {
let parsed = pem::parse(data)?;
let data = parsed.into_contents();
Ok(data)
}

#[cfg(feature = "rcgen")]
pub fn from_rcgen(cert: &rcgen::Certificate) -> (PrivateKey, Certificate) {
let key_pem = cert.serialize_private_key_pem();
let cert_pem = cert.serialize_pem().unwrap();
(PrivateKey(key_pem), Certificate(cert_pem))
}
20 changes: 20 additions & 0 deletions crates/xwt-test-assets-build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "xwt-test-assets-build"
version = "0.1.0"
edition = "2021"
resolver = "2"
license = "MIT"
description = """
A static assets generation utility.
"""
repository = "https://github.com/MOZGIII/xwt"

[target.'cfg(not(target_family = "wasm"))'.dependencies]
xwt-cert-utils = { version = "0.1", path = "../xwt-cert-utils" }

rcgen = "0.11"
tokio = { version = "1", default-features = false, features = ["macros", "rt-multi-thread", "fs"], optional = true }

[features]
default = ["tokio"]
tokio = ["dep:tokio"]
107 changes: 107 additions & 0 deletions crates/xwt-test-assets-build/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#![cfg(not(target_family = "wasm"))]

use std::{
io::ErrorKind,
path::{Path, PathBuf},
};

pub fn generate() -> rcgen::Certificate {
let params = xwt_cert_utils::gen::Params {
common_name: "xwt test certificate",
subject_alt_names: &["localhost", "127.0.0.1", "::1"],
valid_days_before: 0,
valid_days_after: 14,
};

let alg = &rcgen::PKCS_ECDSA_P256_SHA256;
let key = rcgen::KeyPair::generate(alg).unwrap();
params.into_rcgen_cert(alg, key).unwrap()
}

pub fn state_dir() -> PathBuf {
let mut dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap());
dir.push("assets");
println!("{}", dir.display());
dir
}

#[cfg(feature = "tokio")]
pub async fn save_tokio(certificate: rcgen::Certificate, dir: impl AsRef<Path>) {
use tokio::io::AsyncWriteExt;

let dir = dir.as_ref();

tokio::fs::create_dir_all(dir).await.unwrap();

let open = |file| async move {
tokio::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(file)
.await
};

let results = (
open(dir.join("cert.der")).await,
open(dir.join("key.der")).await,
);

match results {
(Ok(mut cert_file), Ok(mut key_file)) => {
cert_file
.write_all(&certificate.serialize_der().unwrap())
.await
.unwrap();
key_file
.write_all(&certificate.serialize_private_key_der())
.await
.unwrap();
cert_file.flush().await.unwrap();
key_file.flush().await.unwrap();
}
(Err(cert_err), Err(key_err))
if cert_err.kind() == ErrorKind::AlreadyExists
&& key_err.kind() == ErrorKind::AlreadyExists => {}
(cert_res, key_res) => {
let _ = cert_res.unwrap();
let _ = key_res.unwrap();
}
}
}

pub fn save(certificate: rcgen::Certificate, dir: impl AsRef<Path>) {
use std::io::Write;

let dir = dir.as_ref();

std::fs::create_dir_all(dir).unwrap();

let open = |file| {
std::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(file)
};

let results = (open(dir.join("cert.der")), open(dir.join("key.der")));

match results {
(Ok(mut cert_file), Ok(mut key_file)) => {
cert_file
.write_all(&certificate.serialize_der().unwrap())
.unwrap();
key_file
.write_all(&certificate.serialize_private_key_der())
.unwrap();
cert_file.flush().unwrap();
key_file.flush().unwrap();
}
(Err(cert_err), Err(key_err))
if cert_err.kind() == ErrorKind::AlreadyExists
&& key_err.kind() == ErrorKind::AlreadyExists => {}
(cert_res, key_res) => {
let _ = cert_res.unwrap();
let _ = key_res.unwrap();
}
}
}
15 changes: 15 additions & 0 deletions crates/xwt-test-assets/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "xwt-test-assets"
version = "0.1.0"
edition = "2021"
resolver = "2"
license = "MIT"
description = """
Static test assets for xwt.
"""
repository = "https://github.com/MOZGIII/xwt"

[dependencies]

[build-dependencies]
xwt-test-assets-build = { version = "0.1", path = "../xwt-test-assets-build", default-features = false }
2 changes: 2 additions & 0 deletions crates/xwt-test-assets/assets/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
6 changes: 6 additions & 0 deletions crates/xwt-test-assets/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
xwt_test_assets_build::save(
xwt_test_assets_build::generate(),
xwt_test_assets_build::state_dir(),
);
}
2 changes: 2 additions & 0 deletions crates/xwt-test-assets/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub const CERT: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/cert.der"));
pub const KEY: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/key.der"));
33 changes: 33 additions & 0 deletions crates/xwt-test-echo-server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "xwt-test-echo-server"
version = "0.2.0"
edition = "2021"
resolver = "2"
license = "MIT"
description = """
The echo server to use for xwt testing.
Not intended to be wasm-compatible.
"""
repository = "https://github.com/MOZGIII/xwt"
autobins = false

[[bin]]
name = "xwt-test-echo-server"
path = "src/main.rs"
required-features = ["bin"]

[target.'cfg(not(target_family = "wasm"))'.dependencies]
xwt-test-assets = { version = "0.1", path = "../xwt-test-assets" }
xwt-cert-utils = { version = "0.1", path = "../xwt-cert-utils" }

color-eyre = { version = "0.6", optional = true }
envfury = { version = "0.2", optional = true }
thiserror = "1"
tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", optional = true }
wtransport = "0.1.8"

[features]
bin = ["dep:color-eyre", "dep:envfury", "dep:tracing-subscriber", "tokio/macros"]
default = ["bin"]
Loading

0 comments on commit 6754d6b

Please sign in to comment.