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:
Keagan McClelland
2021-10-18 17:28:01 -06:00
committed by Aiden McClelland
parent 0f9c20f5f8
commit f7465da797
4 changed files with 87 additions and 16 deletions

View File

@@ -156,6 +156,7 @@ impl RpcContext {
base.tor_control
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
secret_store.clone(),
None,
)
.await?;
let managers = ManagerMap::default();

View File

@@ -1,6 +1,8 @@
use std::net::{Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use openssl::pkey::{PKey, Private};
use openssl::x509::X509;
use rpc_toolkit::command;
use sqlx::SqlitePool;
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
@@ -10,6 +12,7 @@ use self::interface::{Interface, InterfaceId};
#[cfg(feature = "avahi")]
use self::mdns::MdnsController;
use self::nginx::NginxController;
use self::ssl::SslManager;
use self::tor::TorController;
use crate::net::interface::TorConfig;
use crate::net::nginx::InterfaceMetadata;
@@ -42,12 +45,17 @@ impl NetController {
embassyd_tor_key: TorSecretKeyV3,
tor_control: SocketAddr,
db: SqlitePool,
import_root_ca: Option<(PKey<Private>, X509)>,
) -> 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 {
tor: TorController::init(embassyd_addr, embassyd_tor_key, tor_control).await?,
#[cfg(feature = "avahi")]
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?;
Ok(())
}
pub async fn export_root_ca(&self) -> Result<(PKey<Private>, X509), Error> {
self.nginx.ssl_manager.export_root_ca().await
}
}

View File

@@ -18,12 +18,14 @@ use crate::{Error, ErrorKind, ResultExt};
pub struct NginxController {
nginx_root: PathBuf,
pub ssl_manager: SslManager,
inner: Mutex<NginxControllerInner>,
}
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 {
inner: Mutex::new(NginxControllerInner::init(&nginx_root, db).await?),
inner: Mutex::new(NginxControllerInner::init(&nginx_root, &ssl_manager).await?),
ssl_manager,
nginx_root,
})
}
@@ -39,7 +41,13 @@ impl NginxController {
self.inner
.lock()
.await
.add(&self.nginx_root, package, ipv4, interfaces)
.add(
&self.nginx_root,
&self.ssl_manager,
package,
ipv4,
interfaces,
)
.await
}
pub async fn remove(&self, package: &PackageId) -> Result<(), Error> {
@@ -53,22 +61,17 @@ impl NginxController {
pub struct NginxControllerInner {
interfaces: BTreeMap<PackageId, PackageNetInfo>,
ssl_manager: SslManager,
}
impl NginxControllerInner {
#[instrument(skip(db))]
async fn init(nginx_root: &Path, db: SqlitePool) -> Result<Self, Error> {
#[instrument]
async fn init(nginx_root: &Path, ssl_manager: &SslManager) -> Result<Self, Error> {
let inner = NginxControllerInner {
interfaces: BTreeMap::new(),
ssl_manager: SslManager::init(db).await?,
};
let (key, cert) = inner
.ssl_manager
.certificate_for(&get_hostname().await?)
.await?;
let (key, cert) = ssl_manager.certificate_for(&get_hostname().await?).await?;
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"));
futures::try_join!(
tokio::try_join!(
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?),
tokio::fs::write(
&ssl_path_cert,
@@ -83,6 +86,7 @@ impl NginxControllerInner {
async fn add<I: IntoIterator<Item = (InterfaceId, InterfaceMetadata)>>(
&mut self,
nginx_root: &Path,
ssl_manager: &SslManager,
package: PackageId,
ipv4: Ipv4Addr,
interfaces: I,
@@ -108,9 +112,9 @@ impl NginxControllerInner {
nginx_root.join(format!("ssl/{}/{}.key.pem", package, id));
let ssl_path_cert =
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
futures::try_join!(
tokio::try_join!(
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?).map(
|res| res.with_ctx(|_| (
ErrorKind::Filesystem,
@@ -197,7 +201,7 @@ impl NginxControllerInner {
nginx_root.join(format!("sites-enabled/{}_{}.conf", package, id));
let available_path =
nginx_root.join(format!("sites-available/{}_{}.conf", package, id));
let _ = futures::try_join!(
let _ = tokio::try_join!(
async {
if tokio::fs::metadata(&package_path).await.is_ok() {
tokio::fs::remove_dir_all(&package_path)

View File

@@ -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.
#[derive(Debug)]
pub struct SslManager {
store: SslStore,
root_cert: X509,
@@ -24,6 +25,7 @@ pub struct SslManager {
int_cert: X509,
}
#[derive(Debug)]
struct SslStore {
secret_store: SqlitePool,
}
@@ -79,6 +81,19 @@ impl SslStore {
}
}
#[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(
&self,
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))]
pub async fn certificate_for(
&self,