155 lines
4.7 KiB
Rust
155 lines
4.7 KiB
Rust
// Copyright (c) 2019 Reyk Floeter <contact@reykfloeter.com>
|
|
//
|
|
// Permission to use, copy, modify, and distribute this software for any
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
// copyright notice and this permission notice appear in all copies.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
use derive_more::{Deref, From, Into};
|
|
use dirs::home_dir;
|
|
use std::{
|
|
fs::{create_dir, File},
|
|
io::{Error, ErrorKind, Result, Write},
|
|
path::PathBuf,
|
|
process::{Command, Output},
|
|
};
|
|
|
|
pub const COUNTRY: &str = "XX";
|
|
pub const CONFIG_DIR: &str = ".yodle";
|
|
pub const SERVER_CERT: &str = "yodle.crt";
|
|
pub const SERVER_KEY: &str = "yodle.key";
|
|
const SERVER_CSR: &str = "yodle.csr";
|
|
|
|
/// A generic keypair representing a client or the server
|
|
#[derive(Debug, Clone)]
|
|
pub struct Cert {
|
|
pub name: String,
|
|
pub cert: PathBuf,
|
|
pub key: Option<PathBuf>,
|
|
}
|
|
|
|
/// The local server keypair
|
|
#[derive(Clone, Debug, Deref, From, Into)]
|
|
pub struct KeyPair(Cert);
|
|
|
|
impl KeyPair {
|
|
/// Get the server keys or generate them if they don't exist
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function may fail if the key cannot be generated.
|
|
pub fn new() -> Self {
|
|
let home = home_dir().unwrap();
|
|
let mut config_dir = PathBuf::from(home);
|
|
config_dir.push(CONFIG_DIR);
|
|
|
|
debug!("config_dir {}", config_dir.display());
|
|
|
|
let mut key = PathBuf::from(&config_dir);
|
|
key.push(SERVER_KEY);
|
|
let key_path = key.to_string_lossy();
|
|
|
|
let mut cert = PathBuf::from(&config_dir);
|
|
cert.push(SERVER_CERT);
|
|
let cert_path = cert.to_string_lossy();
|
|
|
|
let mut csr = PathBuf::from(&config_dir);
|
|
csr.push(SERVER_CSR);
|
|
let csr_path = csr.to_string_lossy();
|
|
|
|
if !config_dir.exists() {
|
|
create_dir(&config_dir).expect("failed to create config dir");
|
|
}
|
|
|
|
if !key.exists() {
|
|
gen_key(&key_path).expect("failed to generate key");
|
|
}
|
|
|
|
if !csr.exists() {
|
|
gen_csr(&key_path, &csr_path).expect("failed to generate signing request");
|
|
}
|
|
|
|
if !cert.exists() {
|
|
gen_cert(&key_path, &csr_path, &cert_path).expect("failed to generate cert");
|
|
}
|
|
|
|
Cert {
|
|
name: "localhost".into(),
|
|
cert,
|
|
key: Some(key),
|
|
}
|
|
.into()
|
|
}
|
|
}
|
|
|
|
fn command_ok(output: Output) -> Result<()> {
|
|
if output.status.success() {
|
|
Ok(())
|
|
} else {
|
|
Err(Error::new(
|
|
ErrorKind::Other,
|
|
String::from_utf8(output.stderr).unwrap_or("error".to_string()),
|
|
))
|
|
}
|
|
}
|
|
|
|
fn gen_key(key_path: &str) -> Result<()> {
|
|
Command::new("openssl")
|
|
.args(&["ecparam", "-name", "secp384r1", "-genkey", "-out", key_path])
|
|
.output()?;
|
|
Command::new("chmod").args(&["0640", key_path]).output()?;
|
|
Ok(())
|
|
}
|
|
|
|
fn gen_csr(key_path: &str, csr_path: &str) -> Result<()> {
|
|
debug!("key {} csr {}", key_path, csr_path);
|
|
|
|
// XXX use gethostname() libc function or a crate around it
|
|
let hostname = String::from_utf8(Command::new("hostname").output()?.stdout)
|
|
.map_err(|err| Error::new(ErrorKind::Other, err))?;
|
|
let subject = format!("/C={cn}/CN={hn}/O=yodle/", cn = COUNTRY, hn = hostname);
|
|
|
|
command_ok(
|
|
Command::new("openssl")
|
|
.args(&[
|
|
"req", "-new", "-batch", "-subj", &subject, "-key", key_path, "-out", csr_path,
|
|
])
|
|
.output()?,
|
|
)?;
|
|
Command::new("chmod").args(&["0640", csr_path]).output()?;
|
|
|
|
let mut f = File::create(csr_path.to_owned() + ".ext")?;
|
|
f.write_all(format!("subjectAltName=DNS:localhost,DNS:{}", hostname).as_bytes())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn gen_cert(key_path: &str, csr_path: &str, cert_path: &str) -> Result<()> {
|
|
command_ok(
|
|
Command::new("openssl")
|
|
.args(&[
|
|
"x509",
|
|
"-sha384",
|
|
"-req",
|
|
"-days",
|
|
"365",
|
|
"-fingerprint",
|
|
"-in",
|
|
csr_path,
|
|
"-signkey",
|
|
key_path,
|
|
"-out",
|
|
cert_path,
|
|
"-extfile",
|
|
&(csr_path.to_owned() + ".ext"),
|
|
])
|
|
.output()?,
|
|
)
|
|
}
|