use std::path::Path;
use std::time::Duration;
use anyhow::{bail, Result};
use hyper::client::{HttpConnector, ResponseFuture};
use hyper::{Body, Client, Request};
use hyper_openssl::HttpsConnector;
use openssl::ssl::{SslConnector, SslConnectorBuilder, SslFiletype, SslMethod, SslVerifyMode};
#[cfg(feature = "pubsub")]
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder};
use x509_parser::prelude::ParsedExtension;
pub(crate) type HTTPSConnector = HttpsConnector<HttpConnector>;
pub(crate) type HTTPSClient = Client<HTTPSConnector, Body>;
pub(crate) type HTTPClient = Client<HttpConnector, Body>;
pub(crate) type TlsClientConfig = SslConnectorBuilder;
#[derive(Clone, Debug)]
pub(crate) enum ClientInner {
Https(HTTPSClient),
Http(HTTPClient),
}
impl ClientInner {
pub(crate) fn request(&self, req: Request<Body>) -> ResponseFuture {
match self {
ClientInner::Https(c) => c.request(req),
ClientInner::Http(c) => c.request(req),
}
}
}
pub(crate) fn create_client_tls_cfg(
cert_path: impl AsRef<Path>,
pk_path: impl AsRef<Path>,
rootca_path: impl AsRef<Path>,
) -> Result<TlsClientConfig> {
let mut builder = SslConnector::builder(SslMethod::tls_client())?;
log::debug!("Setting CipherSuite");
builder.set_cipher_list("ECDHE-ECDSA-AES128-CCM8")?;
log::debug!("Loading Certificate File");
builder.set_certificate_file(cert_path, SslFiletype::PEM)?;
log::debug!("Loading Private Key File");
builder.set_private_key_file(pk_path, SslFiletype::PEM)?;
log::debug!("Loading Certificate Authority File");
builder.set_ca_file(rootca_path)?;
log::debug!("Setting verification mode");
builder.set_verify(SslVerifyMode::PEER);
Ok(builder)
}
pub(crate) fn create_client(
tls_config: TlsClientConfig,
tcp_keepalive: Option<Duration>,
) -> Client<HTTPSConnector, Body> {
let mut http = HttpConnector::new();
http.enforce_http(false);
http.set_keepalive(tcp_keepalive);
let https = HttpsConnector::with_connector(http, tls_config).unwrap();
Client::builder().build::<HTTPSConnector, hyper::Body>(https)
}
pub(crate) fn create_http_client(tcp_keepalive: Option<Duration>) -> Client<HttpConnector, Body> {
let mut http = HttpConnector::new();
http.enforce_http(false);
http.set_keepalive(tcp_keepalive);
Client::builder().build::<HttpConnector, hyper::Body>(http)
}
#[cfg(feature = "pubsub")]
pub(crate) type TlsServerConfig = SslAcceptorBuilder;
#[cfg(feature = "pubsub")]
pub(crate) fn create_server_tls_config(
cert_path: impl AsRef<Path>,
pk_path: impl AsRef<Path>,
rootca_path: impl AsRef<Path>,
) -> Result<TlsServerConfig> {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls_server())?;
log::debug!("Setting CipherSuite");
builder.set_cipher_list("ECDHE-ECDSA-AES128-CCM8")?;
log::debug!("Loading Certificate File");
builder.set_certificate_file(cert_path, SslFiletype::PEM)?;
log::debug!("Loading Private Key File");
builder.set_private_key_file(pk_path, SslFiletype::PEM)?;
log::debug!("Loading Certificate Authority File");
builder.set_ca_file(rootca_path)?;
log::debug!("Setting verification mode");
builder.set_verify(SslVerifyMode::FAIL_IF_NO_PEER_CERT | SslVerifyMode::PEER);
Ok(builder)
}
pub fn check_device_cert(cert_path: impl AsRef<Path>) -> Result<()> {
let contents = std::fs::read(cert_path)?;
let (_rem, cert) = x509_parser::pem::parse_x509_pem(&contents)?;
let cert = cert.parse_x509()?;
let exts = cert.extensions();
let mut key_usage = false;
let mut certificate_policies = false;
let mut san = false;
let mut aki = false;
for ext in exts {
let critical = ext.critical;
match ext.parsed_extension() {
ParsedExtension::PolicyMappings(_) => {
bail!("Device Certificates cannot contain policy mappings.")
}
ParsedExtension::NameConstraints(_) => {
bail!("Device Certificates cannot contain name constraints.")
}
ParsedExtension::CertificatePolicies(_) => {
if critical {
certificate_policies = true;
} else {
bail!("CertificatePolicies extension must be critical.")
}
}
ParsedExtension::SubjectAlternativeName(_) => {
if critical {
san = true;
} else {
bail!("SubjectAlternativeName extension must be critical")
}
}
ParsedExtension::KeyUsage(_) => {
if critical {
key_usage = true;
} else {
bail!("KeyUsage extension must be critical.")
}
}
ParsedExtension::AuthorityKeyIdentifier(_) => {
if critical {
bail!("AuthorityKeyIdentifier extension cannot be critical.")
} else {
aki = true;
}
}
ParsedExtension::SubjectKeyIdentifier(_) => {
if critical {
bail!("SubjectKeyIdentifier cannot be critical")
}
}
_ => bail!("Unexpected extension or unparsed extension encountered."),
}
}
if !key_usage {
bail!("KeyUsage extension not present")
}
if !certificate_policies {
bail!("CertificatePolicies extension not present")
}
if !san {
bail!("SubjectAlternativeName extension not present")
}
if !aki {
bail!("AuthorityKeyIdentifier extension not present")
}
Ok(())
}
pub fn check_self_signed_client_cert(cert_path: impl AsRef<Path>) -> Result<()> {
let contents = std::fs::read(cert_path)?;
let (_rem, cert) = x509_parser::pem::parse_x509_pem(&contents)?;
let cert = cert.parse_x509()?;
let exts = cert.extensions();
let mut key_usage = false;
let mut certificate_policies = false;
for ext in exts {
let critical = ext.critical;
match ext.parsed_extension() {
ParsedExtension::PolicyMappings(_) => {
bail!("Device Certificates cannot contain policy mappings.")
}
ParsedExtension::NameConstraints(_) => {
bail!("Device Certificates cannot contain name constraints.")
}
ParsedExtension::CertificatePolicies(_) => {
if critical {
certificate_policies = true;
} else {
bail!("CertificatePolicies extension must be critical.")
}
}
ParsedExtension::KeyUsage(_) => {
if critical {
key_usage = true;
} else {
bail!("KeyUsage extension must be critical.")
}
}
ParsedExtension::SubjectKeyIdentifier(_) => {
if critical {
bail!("SubjectKeyIdentifier cannot be critical")
}
}
_ => bail!("Unexpected extension or unparsed extension encountered."),
}
}
if !key_usage {
bail!("KeyUsage extension not present.")
}
if !certificate_policies {
bail!("CertificatePolicies extension not present.")
}
Ok(())
}
pub fn check_ca(cert_path: impl AsRef<Path>) -> Result<()> {
let contents = std::fs::read(cert_path)?;
let (_rem, cert) = x509_parser::pem::parse_x509_pem(&contents)?;
let cert = cert.parse_x509()?;
let exts = cert.extensions();
let mut key_usage = false;
let mut certificate_policies = false;
let mut basic_constraints = false;
let mut ski = false;
for ext in exts {
let critical = ext.critical;
match ext.parsed_extension() {
ParsedExtension::CertificatePolicies(_) => {
if critical {
certificate_policies = true;
} else {
bail!("CertificatePolicies extension must be critical.")
}
}
ParsedExtension::KeyUsage(ku) => {
if critical && ku.crl_sign() && ku.key_cert_sign() {
key_usage = true;
} else {
bail!("KeyUsage extension must be critical and keyCertSign and crlSign must be true.")
}
}
ParsedExtension::BasicConstraints(bc) => {
if critical && bc.path_len_constraint.is_none() && bc.ca {
basic_constraints = true;
} else {
bail!("BasicConstraints must be critical, cA must be true, and pathLen must be absent.")
}
}
ParsedExtension::SubjectKeyIdentifier(_) => {
if critical {
bail!("SubjectKeyIdentifier cannot be critical")
} else {
ski = true;
}
}
_ => bail!("Unexpected extension or unparsed extension encountered."),
}
}
if !key_usage {
bail!("KeyUsage extension not present.")
}
if !certificate_policies {
bail!("CertificatePolicies extension not present.")
}
if !basic_constraints {
bail!("BasicConstraints extension not present.")
}
if !ski {
bail!("SubjectKeyIdentifier extension not present.")
}
Ok(())
}