use std::{borrow::Borrow, sync::Arc};
use anyhow::Result;
use quinn::rustls::sign::{CertifiedKey as RustlsCertifiedKey, SigningKey as RustlsSigningKey};
use rcgen::{CertifiedKey as RcgenCertifiedKey, KeyPair as RcgenKeyPair, PublicKeyData};
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use crate::protocol::{
DataTag as _, TaggedData,
compat::Feature,
control::{Compatibility, CredentialsType},
};
#[allow(missing_debug_implementations)]
pub struct Credentials {
pub keypair: RcgenCertifiedKey<RcgenKeyPair>,
pub hostname: String,
}
impl Credentials {
pub fn generate() -> Result<Self> {
let hostname = gethostname::gethostname()
.into_string()
.unwrap_or("unknown.host.invalid".to_string());
tracing::trace!("Creating certificate with hostname {hostname}");
Ok(Self {
keypair: rcgen::generate_simple_self_signed([hostname.clone()])?,
hostname,
})
}
#[must_use]
pub fn certificate(&self) -> &CertificateDer<'static> {
self.keypair.cert.der()
}
#[must_use]
pub fn private_key_der(&self) -> PrivateKeyDer<'static> {
rustls_pki_types::PrivateKeyDer::Pkcs8(self.keypair.signing_key.serialize_der().into())
}
pub fn as_raw_public_key(&self) -> Result<RustlsCertifiedKey> {
let spki = self.keypair.signing_key.subject_public_key_info();
let public_key_cert = CertificateDer::from(spki);
let kp_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(
self.keypair.signing_key.serialized_der(),
));
let signing_key: Arc<dyn RustlsSigningKey> =
rustls::crypto::ring::sign::any_supported_type(&kp_der)?;
Ok(RustlsCertifiedKey::new(vec![public_key_cert], signing_key))
}
#[must_use]
pub fn type_tag_for(
compat: Compatibility,
configured_type: Option<CredentialsType>,
) -> CredentialsType {
match configured_type {
None | Some(CredentialsType::Any) => (),
Some(other) => return other,
}
if compat.supports(Feature::CMSG_SMSG_2) {
tracing::trace!("selected creds type: Rfc7250");
CredentialsType::RawPublicKey
} else {
tracing::trace!("selected creds type: X509");
CredentialsType::X509
}
}
pub fn to_tagged_data(
&self,
compat: Compatibility,
configured_type: Option<CredentialsType>,
) -> Result<TaggedData<CredentialsType>> {
let tag = Self::type_tag_for(compat, configured_type);
let res = match tag {
CredentialsType::Any => unreachable!(),
CredentialsType::X509 => {
tag.with_bytes(self.certificate())
}
CredentialsType::RawPublicKey => {
anyhow::ensure!(
compat.supports(Feature::CMSG_SMSG_2),
"RawPublicKey credentials configured, but not supported by remote",
);
let key = self.as_raw_public_key()?;
let borrowed: &rustls::sign::CertifiedKey = key.borrow();
tag.with_bytes(&borrowed.cert[0])
}
};
Ok(res)
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use pretty_assertions::assert_eq;
use crate::{
protocol::control::{Compatibility, CredentialsType},
util::Credentials,
};
#[test]
fn generate_works() {
let _ = super::Credentials::generate().unwrap();
}
#[test]
fn type_tag_cases() {
assert_eq!(
Credentials::type_tag_for(Compatibility::Level(3), Some(CredentialsType::RawPublicKey)),
CredentialsType::RawPublicKey
);
assert_eq!(
Credentials::type_tag_for(Compatibility::Level(3), Some(CredentialsType::X509)),
CredentialsType::X509
);
let c = Credentials::generate().unwrap();
let e = c
.to_tagged_data(Compatibility::Level(1), Some(CredentialsType::RawPublicKey))
.unwrap_err();
assert_eq!(
e.to_string(),
"RawPublicKey credentials configured, but not supported by remote"
);
}
}