Add certificate generation tool, make identity passwords optional

This commit is contained in:
htrefil 2020-10-29 17:44:08 +01:00
parent d05d9b7d2a
commit dfde202a7b
6 changed files with 168 additions and 3 deletions

15
Cargo.lock generated
View file

@ -18,6 +18,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "anyhow"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "0.4.7" version = "0.4.7"
@ -99,6 +105,15 @@ version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
[[package]]
name = "certificate-gen"
version = "0.1.0"
dependencies = [
"anyhow",
"structopt",
"tempfile",
]
[[package]] [[package]]
name = "cexpr" name = "cexpr"
version = "0.4.0" version = "0.4.0"

View file

@ -1,2 +1,2 @@
[workspace] [workspace]
members = ["client", "server", "input", "net"] members = ["client", "server", "input", "net", "certificate-gen"]

View file

@ -0,0 +1,12 @@
[package]
name = "certificate-gen"
version = "0.1.0"
authors = ["htrefil <8711792+htrefil@users.noreply.github.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
structopt = "0.3.20"
tempfile = "3.1.0"
anyhow = "1.0.33"

136
certificate-gen/src/main.rs Normal file
View file

@ -0,0 +1,136 @@
use anyhow::{Context, Error};
use std::fmt::Write as _;
use std::io::Write;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::process::{self, Command};
use structopt::StructOpt;
use tempfile::NamedTempFile;
fn run(
identity_path: &Path,
certificate_path: &Path,
key_path: &Path,
dns_names: &[String],
ip_addresses: &[IpAddr],
) -> Result<(), Error> {
if dns_names.is_empty() && ip_addresses.is_empty() {
return Err(anyhow::anyhow!(
"No DNS names nor IP addresses were provided"
));
}
let mut config = "[req]
prompt = no
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = v3_req
[req_distinguished_name]
commonName = rkvm
countryName = CZ
localityName = rkvm
organizationName = rkvm
organizationalUnitName = IT
stateOrProvinceName = rkvm
emailAddress = nowhere@example.com
[req_ext]
subjectAltName = @alt_names
[v3_req]
subjectAltName = @alt_names
[alt_names]"
.to_owned();
for (i, name) in dns_names.iter().enumerate() {
write!(config, "\nDNS.{} = {}", i + 1, name)?;
}
for (i, address) in ip_addresses.iter().enumerate() {
write!(config, "\nIP.{} = {}", i + 1, address)?;
}
let mut file = NamedTempFile::new().context("Failed to open config file")?;
file.write_all(config.as_bytes())
.context("Failed to write to config file")?;
let code = Command::new("openssl")
.arg("req")
.arg("-sha256")
.arg("-x509")
.arg("-nodes")
.arg("-days")
.arg("365")
.arg("-newkey")
.arg("rsa:2048")
.arg("-keyout")
.arg(key_path)
.arg("-out")
.arg(certificate_path)
.arg("-config")
.arg(file.path())
.status()
.context("Failed to launch OpenSSL")?
.code();
if code != Some(0) {
return Err(anyhow::anyhow!("OpenSSL exited unsuccessfully"));
}
let code = Command::new("openssl")
.arg("pkcs12")
.arg("-export")
.arg("-out")
.arg(identity_path)
.arg("-inkey")
.arg(key_path)
.arg("-in")
.arg(certificate_path)
.status()
.context("Failed to launch OpenSSL")?
.code();
if code != Some(0) {
return Err(anyhow::anyhow!("OpenSSL exited unsuccessfully"));
}
Ok(())
}
#[derive(StructOpt)]
#[structopt(
name = "rkvm-certificate-gen",
about = "A tool to generate certificates to use with rkvm"
)]
struct Args {
#[structopt(help = "Path to output identity file (PKCS12 archive)")]
identity_path: PathBuf,
#[structopt(help = "Path to output certificate file (PEM file)")]
certificate_path: PathBuf,
#[structopt(help = "Path to output key file (PEM file)")]
key_path: PathBuf,
#[structopt(
long,
short,
help = "List of DNS names to be used, can be empty if at least one IP address is provided"
)]
dns_names: Vec<String>,
#[structopt(
long,
short,
help = "List of IP addresses to be used, can be empty if at least one DNS name is provided"
)]
ip_addresses: Vec<IpAddr>,
}
fn main() {
let args = Args::from_args();
if let Err(err) = run(
&args.identity_path,
&args.certificate_path,
&args.key_path,
&args.dns_names,
&args.ip_addresses,
) {
println!("Error: {}", err);
process::exit(1);
}
}

View file

@ -1,4 +1,5 @@
listen-address = "0.0.0.0:5258" listen-address = "0.0.0.0:5258"
switch-keys = [29, 56, 111] switch-keys = [29, 56, 111]
identity-path = "identity.p12" identity-path = "identity.p12"
# Doesn't have to be set.
identity-password = "123456789" identity-password = "123456789"

View file

@ -1,13 +1,14 @@
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use std::collections::HashSet; use std::collections::HashSet;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Serialize, Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Config { pub struct Config {
pub listen_address: SocketAddr, pub listen_address: SocketAddr,
pub switch_keys: HashSet<u16>, pub switch_keys: HashSet<u16>,
pub identity_path: PathBuf, pub identity_path: PathBuf,
#[serde(default)]
pub identity_password: String, pub identity_password: String,
} }