use std::{fs, net::ToSocketAddrs, sync::Arc, time::Duration};
use ed25519_dalek::{SigningKey, VerifyingKey};
use eyre::{Report, eyre};
use hex::encode as hex_encode;
use quinn::crypto::rustls::QuicServerConfig;
use quinn::{Endpoint, ServerConfig};
use rand_core::OsRng;
use rcgen::{CertifiedKey, generate_simple_self_signed};
use rustls::ServerConfig as TlsServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use sha2::{Digest, Sha256};
use tokio_rustls::TlsAcceptor;
use crate::{
config::ServerConfigOpts,
keys::{
load_bind_host, load_csk, load_profile_host, load_quic_port, load_signing_key,
load_tcp_port, save_profile_host, secret_dir,
},
};
use volli_core::token;
use volli_core::{DEFAULT_QUIC_PORT, DEFAULT_TCP_PORT, env_config};
#[derive(Debug)]
pub struct SigningInit {
pub key: Arc<SigningKey>,
pub newly_generated: bool,
pub sk_path: Option<std::path::PathBuf>,
pub pk_path: Option<std::path::PathBuf>,
pub id: String,
pub fingerprint: [u8; 32],
}
#[derive(Debug)]
pub struct CertInit {
pub chain: Vec<CertificateDer<'static>>,
pub key: PrivateKeyDer<'static>,
pub fingerprint: String,
pub cert_der: Vec<u8>,
pub key_der: Vec<u8>,
pub cert_path: Option<std::path::PathBuf>,
pub key_path: Option<std::path::PathBuf>,
pub newly_generated: bool,
}
pub fn init_signing(
secret_dir: &std::path::Path,
show_bootstrap: &mut bool,
) -> Result<SigningInit, Report> {
let sk_path = secret_dir.join("manager_sk");
let pk_path = secret_dir.join("manager_pk");
if sk_path.exists() && pk_path.exists() {
let key = load_signing_key(Some(secret_dir))?;
let verifying: VerifyingKey = key.verifying_key();
let id = hex_encode(verifying.to_bytes());
let fp: [u8; 32] = Sha256::digest(verifying.to_bytes()).into();
Ok(SigningInit {
key: Arc::new(key),
newly_generated: false,
sk_path: Some(sk_path),
pk_path: Some(pk_path),
id,
fingerprint: fp,
})
} else {
*show_bootstrap = true;
let mut rng = OsRng;
let key = SigningKey::generate(&mut rng);
let verifying: VerifyingKey = key.verifying_key();
let id = hex_encode(verifying.to_bytes());
let fp: [u8; 32] = Sha256::digest(verifying.to_bytes()).into();
Ok(SigningInit {
key: Arc::new(key),
newly_generated: true,
sk_path: Some(sk_path),
pk_path: Some(pk_path),
id,
fingerprint: fp,
})
}
}
pub fn init_csk(profile: &str) -> Result<([u8; 32], u32, bool), Report> {
match load_csk(profile)? {
Some(v) => Ok((v.0, v.1, false)),
None => {
let mut k = [0u8; 32];
getrandom::fill(&mut k)?;
Ok((k, 1, true))
}
}
}
pub fn init_cert(cfg: &ServerConfigOpts, secret_dir: &std::path::Path) -> Result<CertInit, Report> {
let (cert_fs, key_fs) = if cfg.cert.is_none() && cfg.key.is_none() {
(
Some(secret_dir.join("tls_cert.der")),
Some(secret_dir.join("tls_key.der")),
)
} else {
(
cfg.cert.as_ref().map(std::path::PathBuf::from),
cfg.key.as_ref().map(std::path::PathBuf::from),
)
};
let (c_der, k_der, persist) = if let (Some(c), Some(k)) = (cert_fs.as_ref(), key_fs.as_ref()) {
if c.exists() && k.exists() {
(std::fs::read(c)?, std::fs::read(k)?, false)
} else {
let CertifiedKey { cert, signing_key } =
generate_simple_self_signed(vec!["volli".into()])?;
(cert.der().to_vec(), signing_key.serialize_der(), true)
}
} else {
let CertifiedKey { cert, signing_key } = generate_simple_self_signed(vec!["volli".into()])?;
(cert.der().to_vec(), signing_key.serialize_der(), false)
};
let fp = hex_encode(Sha256::digest(&c_der));
Ok(CertInit {
chain: vec![CertificateDer::from(c_der.clone())],
key: PrivateKeyDer::Pkcs8(k_der.clone().into()),
fingerprint: fp,
cert_der: c_der,
key_der: k_der,
cert_path: cert_fs,
key_path: key_fs,
newly_generated: persist,
})
}
pub fn setup_tls_acceptor(
certs: &[CertificateDer<'static>],
key: &PrivateKeyDer<'static>,
) -> Result<TlsAcceptor, Report> {
let mut config = TlsServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs.to_vec(), key.clone_key())?;
config.alpn_protocols = vec![b"volli/worker".to_vec(), b"volli/manager".to_vec()];
Ok(TlsAcceptor::from(Arc::new(config)))
}
pub fn setup_quic(
host: &str,
port: u16,
certs: &[CertificateDer<'static>],
key: &PrivateKeyDer<'static>,
) -> Result<Endpoint, Report> {
let mut tls = TlsServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs.to_vec(), key.clone_key())?;
tls.alpn_protocols = vec![b"volli/worker".to_vec(), b"volli/manager".to_vec()];
let quic_crypto = QuicServerConfig::try_from(tls)?;
let mut server_config = ServerConfig::with_crypto(Arc::new(quic_crypto));
let ecfg = env_config();
let hb = ecfg.heartbeat_secs();
let mut transport = quinn::TransportConfig::default();
transport.keep_alive_interval(Some(Duration::from_secs(hb)));
transport.max_idle_timeout(Some(ecfg.quic_idle_duration()?.try_into()?));
server_config.transport = Arc::new(transport);
server_config.retry_token_lifetime(Duration::from_millis(1000));
let addr = (host, port)
.to_socket_addrs()?
.next()
.ok_or_else(|| eyre!("invalid bind addr"))?;
let endpoint = Endpoint::server(server_config, addr)?;
Ok(endpoint)
}
#[allow(dead_code)]
pub fn load_or_generate_cert(
cert_path: Option<&str>,
key_path: Option<&str>,
secret_dir: Option<&std::path::Path>,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>, String), Report> {
let (cert_path_fs, key_path_fs) = if cert_path.is_none() && key_path.is_none() {
let dir = secret_dir
.map(std::path::PathBuf::from)
.unwrap_or_else(crate::keys::default_secret_dir);
(
Some(dir.join("tls_cert.der")),
Some(dir.join("tls_key.der")),
)
} else {
(
cert_path.map(std::path::PathBuf::from),
key_path.map(std::path::PathBuf::from),
)
};
let (cert_der, key_der) = if let (Some(c), Some(k)) =
(cert_path_fs.as_ref(), key_path_fs.as_ref())
{
if c.exists() && k.exists() {
(fs::read(c)?, fs::read(k)?)
} else {
if let Some(parent) = c.parent() {
fs::create_dir_all(parent)?;
}
let CertifiedKey { cert, signing_key } =
generate_simple_self_signed(vec!["volli".into()])?;
let cert_der = cert.der().to_vec();
let key_der = signing_key.serialize_der();
fs::write(c, &cert_der)?;
fs::write(k, &key_der)?;
(cert_der, key_der)
}
} else {
let CertifiedKey { cert, signing_key } = generate_simple_self_signed(vec!["volli".into()])?;
(cert.der().to_vec(), signing_key.serialize_der())
};
let fingerprint = hex_encode(Sha256::digest(&cert_der));
Ok((
vec![CertificateDer::from(cert_der)],
PrivateKeyDer::Pkcs8(key_der.into()),
fingerprint,
))
}
pub fn build_secret(
csk: &[u8; 32],
host: &str,
quic_port: u16,
tcp_port: u16,
cert_der: &[u8],
) -> Result<String, Report> {
let token = token::issue_bootstrap_token(
csk,
"self",
"default",
"*",
86_400,
host,
quic_port,
tcp_port,
cert_der.to_vec(),
)?;
token::encode_token(&token)
}
pub fn join_secret(profile: &str) -> Result<String, Report> {
let (csk, _) = load_csk(profile)?.ok_or_else(|| eyre!("missing cluster key"))?;
let host = match load_profile_host(profile)? {
Some(h) => h,
None => {
let fallback = load_bind_host(profile)?.unwrap_or_else(|| "127.0.0.1".into());
let _ = save_profile_host(profile, &fallback);
fallback
}
};
let quic = load_quic_port(profile)?.unwrap_or(DEFAULT_QUIC_PORT);
let tcp = load_tcp_port(profile)?.unwrap_or(DEFAULT_TCP_PORT);
let dir = secret_dir(Some(profile));
let cert_der = std::fs::read(dir.join("tls_cert.der"))?;
build_secret(&csk, &host, quic, tcp, &cert_der)
}