cert/aziot-certd/src/renewal.rs (241 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
#[derive(Clone)]
pub(crate) struct EstIdRenewal {
rotate_key: bool,
credentials: aziot_certd_config::CertificateWithPrivateKey,
path: std::path::PathBuf,
bootstrap_path: Option<(std::path::PathBuf, String)>,
url: url::Url,
basic_auth: Option<aziot_certd_config::EstAuthBasic>,
key_client: std::sync::Arc<aziot_key_client_async::Client>,
key_engine: std::sync::Arc<std::sync::Mutex<openssl2::FunctionalEngine>>,
est_config: std::sync::Arc<tokio::sync::RwLock<crate::est::EstConfig>>,
}
impl EstIdRenewal {
pub async fn new(
cert_id: &str,
credentials: aziot_certd_config::CertificateWithPrivateKey,
api: &crate::Api,
) -> Result<EstIdRenewal, crate::Error> {
let path = aziot_certd_config::util::get_path(
&api.homedir_path,
&api.preloaded_certs,
&credentials.cert,
false,
)
.map_err(|err| crate::Error::Internal(crate::InternalError::GetPath(err)))?;
let (auth, url) = crate::get_est_opts(cert_id, api, None)
.map_err(|err| crate::Error::invalid_parameter("cert_id", err))?;
let bootstrap_path = if let Some(x509) = &auth.x509 {
if let Some(bootstrap) = &x509.bootstrap_identity {
let bootstrap_path = aziot_certd_config::util::get_path(
&api.homedir_path,
&api.preloaded_certs,
&bootstrap.cert,
false,
)
.map_err(|err| crate::Error::Internal(crate::InternalError::GetPath(err)))?;
Some((bootstrap_path, bootstrap.pk.to_string()))
} else {
None
}
} else {
None
};
let rotate_key = {
let est_config = api.est_config.read().await;
est_config.renewal.rotate_key
};
Ok(EstIdRenewal {
rotate_key,
credentials,
path,
bootstrap_path,
url,
basic_auth: auth.basic,
key_client: api.key_client.clone(),
key_engine: api.key_engine.clone(),
est_config: api.est_config.clone(),
})
}
fn load_keys(
&self,
key_handle: aziot_key_common::KeyHandle,
) -> Result<
(
openssl::pkey::PKey<openssl::pkey::Private>,
openssl::pkey::PKey<openssl::pkey::Public>,
),
cert_renewal::Error,
> {
let key_handle = std::ffi::CString::new(key_handle.0)
.map_err(|_| cert_renewal::Error::retryable_error("bad key handle"))?;
let mut key_engine = self.key_engine.lock().expect("mutex poisoned");
let private_key = key_engine
.load_private_key(&key_handle)
.map_err(|_| cert_renewal::Error::retryable_error("failed to load key"))?;
let public_key = key_engine
.load_public_key(&key_handle)
.map_err(|_| cert_renewal::Error::retryable_error("failed to load key"))?;
Ok((private_key, public_key))
}
}
#[async_trait::async_trait]
impl cert_renewal::CertInterface for EstIdRenewal {
type NewKey = String;
#[allow(clippy::unused_async)]
async fn get_cert(
&mut self,
_cert_id: &str,
) -> Result<Vec<openssl::x509::X509>, cert_renewal::Error> {
let cert = std::fs::read(&self.path).map_err(|err| {
cert_renewal::Error::retryable_error(format!("failed to read cert chain: {err}"))
})?;
let cert_chain = openssl::x509::X509::stack_from_pem(&cert)
.map_err(|_| cert_renewal::Error::fatal_error("failed to parse cert chain"))?;
if cert_chain.is_empty() {
Err(cert_renewal::Error::fatal_error("no certs in cert chain"))
} else {
Ok(cert_chain)
}
}
async fn get_key(
&mut self,
key_id: &str,
) -> Result<openssl::pkey::PKey<openssl::pkey::Private>, cert_renewal::Error> {
let key_handle = self
.key_client
.load_key_pair(key_id)
.await
.map_err(|_| cert_renewal::Error::retryable_error("failed to get cert key"))?;
let key_handle = std::ffi::CString::new(key_handle.0)
.map_err(|_| cert_renewal::Error::fatal_error("bad key handle"))?;
let mut key_engine = self.key_engine.lock().expect("mutex poisoned");
key_engine
.load_private_key(&key_handle)
.map_err(|_| cert_renewal::Error::retryable_error("failed to load cert key"))
}
async fn renew_cert(
&mut self,
old_cert_chain: &[openssl::x509::X509],
key_id: &str,
) -> Result<(Vec<openssl::x509::X509>, Self::NewKey), cert_renewal::Error> {
// If the old cert is expired, authenticate with the bootstrap credentials. Otherwise,
// use the old cert to authenticate.
let now = openssl::asn1::Asn1Time::days_from_now(0)
.map_err(|_| cert_renewal::Error::retryable_error("failed to get current time"))?;
let (est_id_cert, est_id_key) = if old_cert_chain[0].not_after() <= now {
if let Some((bootstrap_path, bootstrap_key)) = &self.bootstrap_path {
let bootstrap_cert = std::fs::read(bootstrap_path).map_err(|_| {
cert_renewal::Error::retryable_error("failed to read bootstrap cert")
})?;
(bootstrap_cert, bootstrap_key)
} else {
return Err(cert_renewal::Error::fatal_error(
"bootstrap credentials not available",
));
}
} else {
let cert = old_cert_chain[0]
.to_pem()
.map_err(|_| cert_renewal::Error::fatal_error("bad cert"))?;
(cert, &self.credentials.pk)
};
let est_id_key = self
.key_client
.load_key_pair(est_id_key)
.await
.map_err(|_| cert_renewal::Error::retryable_error("failed to load EST auth key"))?;
let (est_id_key, _) = self
.load_keys(est_id_key)
.map_err(|_| cert_renewal::Error::retryable_error("failed to load EST auth key"))?;
// Generate a new key if needed. Otherwise, retrieve the existing key.
let (key_id, key_handle) = if self.rotate_key {
let key_id = format!("{key_id}-temp");
if let Ok(key_handle) = self.key_client.load_key_pair(&key_id).await {
self.key_client
.delete_key_pair(&key_handle)
.await
.map_err(|_| {
cert_renewal::Error::retryable_error("failed to clear temp key")
})?;
}
let key_handle = self
.key_client
.create_key_pair_if_not_exists(&key_id, Some("ec-p256:rsa-4096:*"))
.await
.map_err(|_| cert_renewal::Error::retryable_error("failed to generate temp key"))?;
(key_id, key_handle)
} else {
let key_handle = self.key_client.load_key_pair(key_id).await.map_err(|_| {
cert_renewal::Error::retryable_error("failed to get identity cert key")
})?;
(key_id.to_string(), key_handle)
};
let keys = self.load_keys(key_handle)?;
let cert = {
let est_config = self.est_config.read().await;
crate::create_est_id(
old_cert_chain[0].subject_name(),
keys,
&self.url,
(est_id_cert, est_id_key),
self.basic_auth.as_ref(),
&est_config.trusted_certs,
est_config.proxy_uri.clone(),
)
.await
.map_err(|err| {
cert_renewal::Error::retryable_error(format!(
"failed to issue new EST identity cert: {err}"
))
})?
};
let cert_chain = openssl::x509::X509::stack_from_pem(&cert)
.map_err(|_| cert_renewal::Error::retryable_error("failed to parse new cert"))?;
if cert_chain.is_empty() {
Err(cert_renewal::Error::retryable_error(
"no certs in cert chain",
))
} else {
Ok((cert_chain, key_id))
}
}
async fn write_credentials(
&mut self,
old_cert_chain: &[openssl::x509::X509],
new_cert_chain: (&str, &[openssl::x509::X509]),
key: (&str, Self::NewKey),
) -> Result<(), cert_renewal::Error> {
let new_cert_chain = new_cert_chain.1;
let (old_key, new_key) = (key.0, key.1);
if old_cert_chain.is_empty() || new_cert_chain.is_empty() {
return Err(cert_renewal::Error::retryable_error(
"no certs in cert chain",
));
}
let mut new_cert_chain_pem = Vec::new();
for cert in new_cert_chain {
let mut cert = cert
.to_pem()
.map_err(|_| cert_renewal::Error::retryable_error("bad cert"))?;
new_cert_chain_pem.append(&mut cert);
}
let mut old_cert_chain_pem = Vec::new();
for cert in old_cert_chain {
let mut cert = cert
.to_pem()
.map_err(|_| cert_renewal::Error::retryable_error("bad cert"))?;
old_cert_chain_pem.append(&mut cert);
}
// Commit the new cert to storage.
std::fs::write(&self.path, &new_cert_chain_pem)
.map_err(|_| cert_renewal::Error::retryable_error("failed to import new cert"))?;
// Commit the new key to storage if the key was rotated.
if old_key != new_key
&& self
.key_client
.move_key_pair(&new_key, old_key)
.await
.is_err()
{
// Revert to the previous cert if the key could not be written.
std::fs::write(&self.path, &old_cert_chain_pem)
.map_err(|_| cert_renewal::Error::retryable_error("failed to restore old cert"))?;
}
Ok(())
}
}