use log::debug;
use std::io;
use std::{fs::File, path::Path};
use openssl::asn1::Asn1Time;
use openssl::bn::{BigNum, MsbOption};
use openssl::hash::MessageDigest;
use openssl::pkcs12::Pkcs12;
use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa;
use openssl::stack::Stack;
use openssl::x509::extension::SubjectAlternativeName;
use openssl::x509::{GeneralNameRef, X509Name, X509NameBuilder, X509NameRef, X509};
use crate::error::Error;
pub struct CertificateAuthority {
pub cert: X509,
pub key: PKey<Private>,
}
impl CertificateAuthority {
pub fn load_from_pem_files<P: AsRef<Path>, Q: AsRef<Path>>(
cert_file: P,
key_file: Q,
) -> Result<Self, Error> {
let cert = X509::from_pem(&get_bytes_from_file(cert_file)?)?;
let key = get_bytes_from_file(key_file)?;
let key = PKey::from_rsa(Rsa::private_key_from_pem(&key)?)?;
Ok(Self { cert, key })
}
pub fn load_from_pem_files_with_passphrase_on_key<P: AsRef<Path>, Q: AsRef<Path>>(
cert_file: P,
key_file: Q,
passphrase: &str,
) -> Result<Self, Error> {
let cert = X509::from_pem(&get_bytes_from_file(cert_file)?)?;
let key = get_bytes_from_file(key_file)?;
let key = PKey::from_rsa(Rsa::private_key_from_pem_passphrase(
&key,
passphrase.as_bytes(),
)?)?;
Ok(Self { cert, key })
}
}
fn get_bytes_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Error> {
let mut file = File::open(path)?;
let mut bytes: Vec<u8> = vec![];
io::copy(&mut file, &mut bytes)?;
Ok(bytes)
}
pub(crate) fn native_identity(
certificate: &X509,
key: &PKey<Private>,
) -> Result<native_tls::Identity, Error> {
let pkcs = Pkcs12::builder()
.build(&"third-wheel", &"", key, certificate)?
.to_der()?;
let identity = native_tls::Identity::from_pkcs12(&pkcs, &"third-wheel")?;
Ok(identity)
}
pub fn create_signed_certificate_for_domain(
domain: &str,
ca: &CertificateAuthority,
) -> Result<X509, Error> {
let mut cert_builder = X509::builder()?;
let mut host_name = X509Name::builder()?;
host_name.append_entry_by_text("CN", domain)?;
let host_name = host_name.build();
cert_builder.set_subject_name(&host_name)?;
cert_builder.set_version(2)?;
cert_builder.set_not_before((Asn1Time::days_from_now(0)?).as_ref())?;
cert_builder.set_not_after((Asn1Time::days_from_now(365)?).as_ref())?;
let serial_number = {
let mut serial_number = BigNum::new()?;
serial_number.rand(159, MsbOption::MAYBE_ZERO, false)?;
serial_number.to_asn1_integer()?
};
cert_builder.set_serial_number(&serial_number)?;
let subject_alternative_name = SubjectAlternativeName::new()
.dns(domain)
.build(&cert_builder.x509v3_context(Some(&ca.cert), None))?;
cert_builder.append_extension(subject_alternative_name)?;
cert_builder.set_issuer_name(&ca.cert.issuer_name())?;
cert_builder.set_pubkey(&ca.key)?;
cert_builder.sign(&ca.key, MessageDigest::sha256())?;
Ok(cert_builder.build())
}
fn copy_name(in_name: &X509NameRef) -> Result<X509Name, Error> {
let mut copy: X509NameBuilder = X509Name::builder()?;
for entry in in_name.entries() {
copy.append_entry_by_nid(
entry.object().nid(),
entry
.data()
.as_utf8()
.expect("Expected string as entry in name")
.as_ref(),
)
.expect("Failed to add entry by nid");
}
Ok(copy.build())
}
fn copy_alt_names(in_cert: &X509) -> Option<SubjectAlternativeName> {
match in_cert.subject_alt_names() {
Some(in_alt_names) => {
let mut subject_alternative_name = SubjectAlternativeName::new();
for gn in in_alt_names {
if let Some(email) = gn.email() {
subject_alternative_name.email(email);
} else if let Some(dns) = gn.dnsname() {
subject_alternative_name.dns(dns);
} else if let Some(uri) = gn.uri() {
subject_alternative_name.uri(uri);
} else if let Some(ipaddress) = gn.ipaddress() {
subject_alternative_name.ip(&String::from_utf8(ipaddress.to_vec())
.expect("ip address on certificate is not formatted as ascii"));
}
}
Some(subject_alternative_name)
}
None => None,
}
}
pub(crate) fn spoof_certificate(
certificate: &X509,
ca: &CertificateAuthority,
) -> Result<X509, Error> {
let mut cert_builder = X509::builder()?;
let name: &X509NameRef = certificate.subject_name();
let host_name = copy_name(name)?;
cert_builder.set_subject_name(&host_name)?;
cert_builder.set_not_before(certificate.not_before())?;
cert_builder.set_not_after(certificate.not_after())?;
cert_builder.set_serial_number(certificate.serial_number())?;
cert_builder.set_version(2)?;
if let Some(subject_alternative_name) = copy_alt_names(certificate) {
let subject_alternative_name =
subject_alternative_name.build(&cert_builder.x509v3_context(Some(&ca.cert), None))?;
cert_builder.append_extension(subject_alternative_name)?;
}
cert_builder.set_issuer_name(&ca.cert.issuer_name())?;
cert_builder.set_pubkey(&ca.key)?;
cert_builder.sign(&ca.key, MessageDigest::sha256())?;
Ok(cert_builder.build())
}
#[allow(dead_code)]
fn print_certificate(certificate: &X509) {
debug!("New certificate");
debug!("subject_name:");
for entry in certificate.subject_name().entries() {
debug!("{}: {}", entry.object(), entry.data().as_utf8().unwrap());
}
debug!("issuer_name:");
for entry in certificate.issuer_name().entries() {
debug!("{}: {}", entry.object(), entry.data().as_utf8().unwrap());
}
debug!("subject_alt_names");
for general_name in certificate
.subject_alt_names()
.unwrap_or_else(|| Stack::new().unwrap())
.iter()
{
print_general_name(general_name);
}
debug!("issuer_alt_names");
for general_name in certificate
.issuer_alt_names()
.unwrap_or_else(|| Stack::new().unwrap())
.iter()
{
print_general_name(general_name);
}
debug!("public_key: {:?}", certificate.public_key());
debug!("not_after: {}", certificate.not_after());
debug!("not_before: {}", certificate.not_before());
debug!("Signature: ");
debug!("{:x?}", certificate.signature().as_slice());
debug!(
"Signature algorithm: {}",
certificate.signature_algorithm().object()
);
debug!("ocsp_responders:");
let responders = certificate.ocsp_responders();
match responders {
Ok(stack) => {
for responder in stack.iter() {
debug!("{:?}", responder);
}
}
Err(err) => debug!("Responders threw error: {}", err),
}
let serial_number = certificate.serial_number().to_bn();
match serial_number {
Ok(sn) => debug!("{}", sn),
Err(err) => debug!("Responders threw error: {}", err),
}
}
fn print_general_name(general_name: &GeneralNameRef) {
if let Some(email) = general_name.email() {
debug!("email: {}", email);
}
if let Some(dnsname) = general_name.dnsname() {
debug!("dnsname: {}", dnsname);
}
if let Some(uri) = general_name.uri() {
debug!("uri: {}", uri);
}
if let Some(ipaddress) = general_name.ipaddress() {
debug!("ipaddress: {:?}", ipaddress);
}
}