mirror of https://github.com/breard-r/acmed.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
8.0 KiB
252 lines
8.0 KiB
use crate::certificate::Certificate;
|
|
use crate::config::HookType;
|
|
use crate::hooks::{self, FileStorageHookData};
|
|
use acme_common::b64_encode;
|
|
use acme_common::error::Error;
|
|
use log::trace;
|
|
use openssl::pkey::{PKey, Private, Public};
|
|
use openssl::x509::X509;
|
|
use std::fmt;
|
|
use std::fs::{File, OpenOptions};
|
|
use std::io::{Read, Write};
|
|
use std::path::PathBuf;
|
|
|
|
#[cfg(target_family = "unix")]
|
|
use std::os::unix::fs::OpenOptionsExt;
|
|
|
|
#[derive(Clone)]
|
|
enum FileType {
|
|
AccountPrivateKey,
|
|
AccountPublicKey,
|
|
PrivateKey,
|
|
Certificate,
|
|
}
|
|
|
|
impl fmt::Display for FileType {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s = match self {
|
|
FileType::AccountPrivateKey => "priv-key",
|
|
FileType::AccountPublicKey => "pub-key",
|
|
FileType::PrivateKey => "pk",
|
|
FileType::Certificate => "crt",
|
|
};
|
|
write!(f, "{}", s)
|
|
}
|
|
}
|
|
|
|
fn get_file_full_path(
|
|
cert: &Certificate,
|
|
file_type: FileType,
|
|
) -> Result<(String, String, PathBuf), Error> {
|
|
let base_path = match file_type {
|
|
FileType::AccountPrivateKey | FileType::AccountPublicKey => &cert.account_directory,
|
|
FileType::PrivateKey => &cert.crt_directory,
|
|
FileType::Certificate => &cert.crt_directory,
|
|
};
|
|
let file_name = match file_type {
|
|
FileType::AccountPrivateKey | FileType::AccountPublicKey => format!(
|
|
"{account}.{file_type}.{ext}",
|
|
account = b64_encode(&cert.account.name),
|
|
file_type = file_type.to_string(),
|
|
ext = "pem"
|
|
),
|
|
FileType::PrivateKey | FileType::Certificate => {
|
|
// TODO: use cert.crt_name_format instead of a string literal
|
|
format!(
|
|
"{name}_{algo}.{file_type}.{ext}",
|
|
name = cert.crt_name,
|
|
algo = cert.algo.to_string(),
|
|
file_type = file_type.to_string(),
|
|
ext = "pem"
|
|
)
|
|
}
|
|
};
|
|
let mut path = PathBuf::from(&base_path);
|
|
path.push(&file_name);
|
|
Ok((base_path.to_string(), file_name, path))
|
|
}
|
|
|
|
fn get_file_path(cert: &Certificate, file_type: FileType) -> Result<PathBuf, Error> {
|
|
let (_, _, path) = get_file_full_path(cert, file_type)?;
|
|
Ok(path)
|
|
}
|
|
|
|
fn read_file(path: &PathBuf) -> Result<Vec<u8>, Error> {
|
|
trace!("Reading file {:?}", path);
|
|
let mut file = File::open(path)?;
|
|
let mut contents = vec![];
|
|
file.read_to_end(&mut contents)?;
|
|
Ok(contents)
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn set_owner(cert: &Certificate, path: &PathBuf, file_type: FileType) -> Result<(), Error> {
|
|
let (uid, gid) = match file_type {
|
|
FileType::Certificate => (
|
|
cert.cert_file_owner.to_owned(),
|
|
cert.cert_file_group.to_owned(),
|
|
),
|
|
FileType::PrivateKey => (cert.pk_file_owner.to_owned(), cert.pk_file_group.to_owned()),
|
|
FileType::AccountPrivateKey | FileType::AccountPublicKey => {
|
|
// The account private and public keys does not need to be accessible to users other different from the current one.
|
|
return Ok(());
|
|
}
|
|
};
|
|
let uid = match uid {
|
|
Some(u) => {
|
|
if u.bytes().all(|b| b.is_ascii_digit()) {
|
|
let raw_uid = u.parse::<u32>().unwrap();
|
|
let nix_uid = nix::unistd::Uid::from_raw(raw_uid);
|
|
Some(nix_uid)
|
|
} else {
|
|
// TODO: handle username
|
|
None
|
|
}
|
|
}
|
|
None => None,
|
|
};
|
|
let gid = match gid {
|
|
Some(g) => {
|
|
if g.bytes().all(|b| b.is_ascii_digit()) {
|
|
let raw_gid = g.parse::<u32>().unwrap();
|
|
let nix_gid = nix::unistd::Gid::from_raw(raw_gid);
|
|
Some(nix_gid)
|
|
} else {
|
|
// TODO: handle group name
|
|
None
|
|
}
|
|
}
|
|
None => None,
|
|
};
|
|
match uid {
|
|
Some(u) => trace!("Setting the uid to {}", u.as_raw()),
|
|
None => trace!("Uid unchanged"),
|
|
};
|
|
match gid {
|
|
Some(g) => trace!("Setting the gid to {}", g.as_raw()),
|
|
None => trace!("Gid unchanged"),
|
|
};
|
|
match nix::unistd::chown(path, uid, gid) {
|
|
Ok(_) => Ok(()),
|
|
Err(e) => Err(format!("{}", e).into()),
|
|
}
|
|
}
|
|
|
|
fn write_file(cert: &Certificate, file_type: FileType, data: &[u8]) -> Result<(), Error> {
|
|
let (file_directory, file_name, path) = get_file_full_path(cert, file_type.clone())?;
|
|
let hook_data = FileStorageHookData {
|
|
file_name,
|
|
file_directory,
|
|
file_path: path.to_path_buf(),
|
|
};
|
|
let is_new = !path.is_file();
|
|
|
|
if is_new {
|
|
hooks::call(&hook_data, &cert.hooks, HookType::FilePreCreate)?;
|
|
} else {
|
|
hooks::call(&hook_data, &cert.hooks, HookType::FilePreEdit)?;
|
|
}
|
|
|
|
trace!("Writing file {:?}", path);
|
|
let mut file = if cfg!(unix) {
|
|
let mut options = OpenOptions::new();
|
|
options.mode(match &file_type {
|
|
FileType::Certificate => cert.cert_file_mode,
|
|
FileType::PrivateKey => cert.pk_file_mode,
|
|
FileType::AccountPublicKey => crate::DEFAULT_ACCOUNT_FILE_MODE,
|
|
FileType::AccountPrivateKey => crate::DEFAULT_ACCOUNT_FILE_MODE,
|
|
});
|
|
options.write(true).create(true).open(&path)?
|
|
} else {
|
|
File::create(&path)?
|
|
};
|
|
file.write_all(data)?;
|
|
if cfg!(unix) {
|
|
set_owner(cert, &path, file_type)?;
|
|
}
|
|
|
|
if is_new {
|
|
hooks::call(&hook_data, &cert.hooks, HookType::FilePostCreate)?;
|
|
} else {
|
|
hooks::call(&hook_data, &cert.hooks, HookType::FilePostEdit)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_account_priv_key(cert: &Certificate) -> Result<PKey<Private>, Error> {
|
|
let path = get_file_path(cert, FileType::AccountPrivateKey)?;
|
|
let raw_key = read_file(&path)?;
|
|
let key = PKey::private_key_from_pem(&raw_key)?;
|
|
Ok(key)
|
|
}
|
|
|
|
pub fn set_account_priv_key(cert: &Certificate, key: &PKey<Private>) -> Result<(), Error> {
|
|
let data = key.private_key_to_pem_pkcs8()?;
|
|
write_file(cert, FileType::AccountPrivateKey, &data)
|
|
}
|
|
|
|
pub fn get_account_pub_key(cert: &Certificate) -> Result<PKey<Public>, Error> {
|
|
let path = get_file_path(cert, FileType::AccountPublicKey)?;
|
|
let raw_key = read_file(&path)?;
|
|
let key = PKey::public_key_from_pem(&raw_key)?;
|
|
Ok(key)
|
|
}
|
|
|
|
pub fn set_account_pub_key(cert: &Certificate, key: &PKey<Public>) -> Result<(), Error> {
|
|
let data = key.public_key_to_pem()?;
|
|
write_file(cert, FileType::AccountPublicKey, &data)
|
|
}
|
|
|
|
pub fn get_priv_key(cert: &Certificate) -> Result<PKey<Private>, Error> {
|
|
let path = get_file_path(cert, FileType::PrivateKey)?;
|
|
let raw_key = read_file(&path)?;
|
|
let key = PKey::private_key_from_pem(&raw_key)?;
|
|
Ok(key)
|
|
}
|
|
|
|
pub fn set_priv_key(cert: &Certificate, key: &PKey<Private>) -> Result<(), Error> {
|
|
let data = key.private_key_to_pem_pkcs8()?;
|
|
write_file(cert, FileType::PrivateKey, &data)
|
|
}
|
|
|
|
pub fn get_pub_key(cert: &Certificate) -> Result<PKey<Public>, Error> {
|
|
let pub_key = get_certificate(cert)?.public_key()?;
|
|
Ok(pub_key)
|
|
}
|
|
|
|
pub fn get_certificate(cert: &Certificate) -> Result<X509, Error> {
|
|
let path = get_file_path(cert, FileType::Certificate)?;
|
|
let raw_crt = read_file(&path)?;
|
|
let crt = X509::from_pem(&raw_crt)?;
|
|
Ok(crt)
|
|
}
|
|
|
|
pub fn write_certificate(cert: &Certificate, data: &[u8]) -> Result<(), Error> {
|
|
write_file(cert, FileType::Certificate, data)
|
|
}
|
|
|
|
fn check_files(cert: &Certificate, file_types: &[FileType]) -> bool {
|
|
for t in file_types.to_vec() {
|
|
let path = match get_file_path(cert, t) {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
return false;
|
|
}
|
|
};
|
|
trace!("Testing file path: {}", path.to_str().unwrap());
|
|
if !path.is_file() {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub fn account_files_exists(cert: &Certificate) -> bool {
|
|
let file_types = vec![FileType::AccountPrivateKey, FileType::AccountPublicKey];
|
|
check_files(cert, &file_types)
|
|
}
|
|
|
|
pub fn certificate_files_exists(cert: &Certificate) -> bool {
|
|
let file_types = vec![FileType::PrivateKey, FileType::Certificate];
|
|
check_files(cert, &file_types)
|
|
}
|