mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Certificate Imports/Exports (#687)
* allows for root/int certificate imports, certain invariants are not checked at this time * refactor ssl module to only import root certificate * implements certificate export, propagates import functionality all the way to NetController::init
This commit is contained in:
committed by
Aiden McClelland
parent
0f9c20f5f8
commit
f7465da797
@@ -156,6 +156,7 @@ impl RpcContext {
|
|||||||
base.tor_control
|
base.tor_control
|
||||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
||||||
secret_store.clone(),
|
secret_store.clone(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let managers = ManagerMap::default();
|
let managers = ManagerMap::default();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::net::{Ipv4Addr, SocketAddr};
|
use std::net::{Ipv4Addr, SocketAddr};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use openssl::pkey::{PKey, Private};
|
||||||
|
use openssl::x509::X509;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||||
@@ -10,6 +12,7 @@ use self::interface::{Interface, InterfaceId};
|
|||||||
#[cfg(feature = "avahi")]
|
#[cfg(feature = "avahi")]
|
||||||
use self::mdns::MdnsController;
|
use self::mdns::MdnsController;
|
||||||
use self::nginx::NginxController;
|
use self::nginx::NginxController;
|
||||||
|
use self::ssl::SslManager;
|
||||||
use self::tor::TorController;
|
use self::tor::TorController;
|
||||||
use crate::net::interface::TorConfig;
|
use crate::net::interface::TorConfig;
|
||||||
use crate::net::nginx::InterfaceMetadata;
|
use crate::net::nginx::InterfaceMetadata;
|
||||||
@@ -42,12 +45,17 @@ impl NetController {
|
|||||||
embassyd_tor_key: TorSecretKeyV3,
|
embassyd_tor_key: TorSecretKeyV3,
|
||||||
tor_control: SocketAddr,
|
tor_control: SocketAddr,
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
|
import_root_ca: Option<(PKey<Private>, X509)>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
let ssl_manager = match import_root_ca {
|
||||||
|
None => SslManager::init(db).await,
|
||||||
|
Some(a) => SslManager::import_root_ca(db, a.0, a.1).await,
|
||||||
|
}?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
tor: TorController::init(embassyd_addr, embassyd_tor_key, tor_control).await?,
|
tor: TorController::init(embassyd_addr, embassyd_tor_key, tor_control).await?,
|
||||||
#[cfg(feature = "avahi")]
|
#[cfg(feature = "avahi")]
|
||||||
mdns: MdnsController::init(),
|
mdns: MdnsController::init(),
|
||||||
nginx: NginxController::init(PathBuf::from("/etc/nginx"), db).await?,
|
nginx: NginxController::init(PathBuf::from("/etc/nginx"), ssl_manager).await?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,4 +137,8 @@ impl NetController {
|
|||||||
nginx_res?;
|
nginx_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn export_root_ca(&self) -> Result<(PKey<Private>, X509), Error> {
|
||||||
|
self.nginx.ssl_manager.export_root_ca().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ use crate::{Error, ErrorKind, ResultExt};
|
|||||||
|
|
||||||
pub struct NginxController {
|
pub struct NginxController {
|
||||||
nginx_root: PathBuf,
|
nginx_root: PathBuf,
|
||||||
|
pub ssl_manager: SslManager,
|
||||||
inner: Mutex<NginxControllerInner>,
|
inner: Mutex<NginxControllerInner>,
|
||||||
}
|
}
|
||||||
impl NginxController {
|
impl NginxController {
|
||||||
pub async fn init(nginx_root: PathBuf, db: SqlitePool) -> Result<Self, Error> {
|
pub async fn init(nginx_root: PathBuf, ssl_manager: SslManager) -> Result<Self, Error> {
|
||||||
Ok(NginxController {
|
Ok(NginxController {
|
||||||
inner: Mutex::new(NginxControllerInner::init(&nginx_root, db).await?),
|
inner: Mutex::new(NginxControllerInner::init(&nginx_root, &ssl_manager).await?),
|
||||||
|
ssl_manager,
|
||||||
nginx_root,
|
nginx_root,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -39,7 +41,13 @@ impl NginxController {
|
|||||||
self.inner
|
self.inner
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.add(&self.nginx_root, package, ipv4, interfaces)
|
.add(
|
||||||
|
&self.nginx_root,
|
||||||
|
&self.ssl_manager,
|
||||||
|
package,
|
||||||
|
ipv4,
|
||||||
|
interfaces,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
pub async fn remove(&self, package: &PackageId) -> Result<(), Error> {
|
pub async fn remove(&self, package: &PackageId) -> Result<(), Error> {
|
||||||
@@ -53,22 +61,17 @@ impl NginxController {
|
|||||||
|
|
||||||
pub struct NginxControllerInner {
|
pub struct NginxControllerInner {
|
||||||
interfaces: BTreeMap<PackageId, PackageNetInfo>,
|
interfaces: BTreeMap<PackageId, PackageNetInfo>,
|
||||||
ssl_manager: SslManager,
|
|
||||||
}
|
}
|
||||||
impl NginxControllerInner {
|
impl NginxControllerInner {
|
||||||
#[instrument(skip(db))]
|
#[instrument]
|
||||||
async fn init(nginx_root: &Path, db: SqlitePool) -> Result<Self, Error> {
|
async fn init(nginx_root: &Path, ssl_manager: &SslManager) -> Result<Self, Error> {
|
||||||
let inner = NginxControllerInner {
|
let inner = NginxControllerInner {
|
||||||
interfaces: BTreeMap::new(),
|
interfaces: BTreeMap::new(),
|
||||||
ssl_manager: SslManager::init(db).await?,
|
|
||||||
};
|
};
|
||||||
let (key, cert) = inner
|
let (key, cert) = ssl_manager.certificate_for(&get_hostname().await?).await?;
|
||||||
.ssl_manager
|
|
||||||
.certificate_for(&get_hostname().await?)
|
|
||||||
.await?;
|
|
||||||
let ssl_path_key = nginx_root.join(format!("ssl/embassy_main.key.pem"));
|
let ssl_path_key = nginx_root.join(format!("ssl/embassy_main.key.pem"));
|
||||||
let ssl_path_cert = nginx_root.join(format!("ssl/embassy_main.cert.pem"));
|
let ssl_path_cert = nginx_root.join(format!("ssl/embassy_main.cert.pem"));
|
||||||
futures::try_join!(
|
tokio::try_join!(
|
||||||
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?),
|
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?),
|
||||||
tokio::fs::write(
|
tokio::fs::write(
|
||||||
&ssl_path_cert,
|
&ssl_path_cert,
|
||||||
@@ -83,6 +86,7 @@ impl NginxControllerInner {
|
|||||||
async fn add<I: IntoIterator<Item = (InterfaceId, InterfaceMetadata)>>(
|
async fn add<I: IntoIterator<Item = (InterfaceId, InterfaceMetadata)>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
nginx_root: &Path,
|
nginx_root: &Path,
|
||||||
|
ssl_manager: &SslManager,
|
||||||
package: PackageId,
|
package: PackageId,
|
||||||
ipv4: Ipv4Addr,
|
ipv4: Ipv4Addr,
|
||||||
interfaces: I,
|
interfaces: I,
|
||||||
@@ -108,9 +112,9 @@ impl NginxControllerInner {
|
|||||||
nginx_root.join(format!("ssl/{}/{}.key.pem", package, id));
|
nginx_root.join(format!("ssl/{}/{}.key.pem", package, id));
|
||||||
let ssl_path_cert =
|
let ssl_path_cert =
|
||||||
nginx_root.join(format!("ssl/{}/{}.cert.pem", package, id));
|
nginx_root.join(format!("ssl/{}/{}.cert.pem", package, id));
|
||||||
let (key, chain) = self.ssl_manager.certificate_for(&meta.dns_base).await?;
|
let (key, chain) = ssl_manager.certificate_for(&meta.dns_base).await?;
|
||||||
// write nginx ssl certs
|
// write nginx ssl certs
|
||||||
futures::try_join!(
|
tokio::try_join!(
|
||||||
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?).map(
|
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?).map(
|
||||||
|res| res.with_ctx(|_| (
|
|res| res.with_ctx(|_| (
|
||||||
ErrorKind::Filesystem,
|
ErrorKind::Filesystem,
|
||||||
@@ -197,7 +201,7 @@ impl NginxControllerInner {
|
|||||||
nginx_root.join(format!("sites-enabled/{}_{}.conf", package, id));
|
nginx_root.join(format!("sites-enabled/{}_{}.conf", package, id));
|
||||||
let available_path =
|
let available_path =
|
||||||
nginx_root.join(format!("sites-available/{}_{}.conf", package, id));
|
nginx_root.join(format!("sites-available/{}_{}.conf", package, id));
|
||||||
let _ = futures::try_join!(
|
let _ = tokio::try_join!(
|
||||||
async {
|
async {
|
||||||
if tokio::fs::metadata(&package_path).await.is_ok() {
|
if tokio::fs::metadata(&package_path).await.is_ok() {
|
||||||
tokio::fs::remove_dir_all(&package_path)
|
tokio::fs::remove_dir_all(&package_path)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use crate::{Error, ErrorKind};
|
|||||||
|
|
||||||
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
|
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct SslManager {
|
pub struct SslManager {
|
||||||
store: SslStore,
|
store: SslStore,
|
||||||
root_cert: X509,
|
root_cert: X509,
|
||||||
@@ -24,6 +25,7 @@ pub struct SslManager {
|
|||||||
int_cert: X509,
|
int_cert: X509,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct SslStore {
|
struct SslStore {
|
||||||
secret_store: SqlitePool,
|
secret_store: SqlitePool,
|
||||||
}
|
}
|
||||||
@@ -79,6 +81,19 @@ impl SslStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
|
async fn import_root_certificate(
|
||||||
|
&self,
|
||||||
|
root_key: &PKey<Private>,
|
||||||
|
root_cert: &X509,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// remove records for both root and intermediate CA
|
||||||
|
sqlx::query!("DELETE FROM certificates WHERE id = 0 OR id = 1;")
|
||||||
|
.execute(&self.secret_store)
|
||||||
|
.await?;
|
||||||
|
self.save_root_certificate(root_key, root_cert).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[instrument(skip(self))]
|
||||||
async fn save_certificate(
|
async fn save_certificate(
|
||||||
&self,
|
&self,
|
||||||
key: &PKey<Private>,
|
key: &PKey<Private>,
|
||||||
@@ -170,6 +185,45 @@ impl SslManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: currently the burden of proof is on the caller to ensure that all of the arguments to this function are
|
||||||
|
// consistent. The following properties are assumed and not verified:
|
||||||
|
// 1. `root_cert` is self-signed and contains the public key that matches the private key `root_key`
|
||||||
|
// 2. certificate is not past its expiration date
|
||||||
|
// Warning: If this function ever fails, you must either call it again or regenerate your certificates from scratch
|
||||||
|
// since it is possible for it to fail after successfully saving the root certificate but before successfully saving
|
||||||
|
// the intermediate certificate
|
||||||
|
#[instrument(skip(db))]
|
||||||
|
pub async fn import_root_ca(
|
||||||
|
db: SqlitePool,
|
||||||
|
root_key: PKey<Private>,
|
||||||
|
root_cert: X509,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let store = SslStore::new(db)?;
|
||||||
|
store.import_root_certificate(&root_key, &root_cert).await?;
|
||||||
|
let int_key = generate_key()?;
|
||||||
|
let int_cert = make_int_cert((&root_key, &root_cert), &int_key)?;
|
||||||
|
store
|
||||||
|
.save_intermediate_certificate(&int_key, &int_cert)
|
||||||
|
.await?;
|
||||||
|
Ok(SslManager {
|
||||||
|
store,
|
||||||
|
root_cert,
|
||||||
|
int_key,
|
||||||
|
int_cert,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn export_root_ca(&self) -> Result<(PKey<Private>, X509), Error> {
|
||||||
|
match self.store.load_root_certificate().await? {
|
||||||
|
None => Err(Error::new(
|
||||||
|
eyre!("Failed to export root certificate: root certificate has not been generated"),
|
||||||
|
ErrorKind::OpenSsl,
|
||||||
|
)),
|
||||||
|
Some(a) => Ok(a),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub async fn certificate_for(
|
pub async fn certificate_for(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
Reference in New Issue
Block a user