use std::path::Path;
use std::{fs, io};
use log::debug;
use rustls::{Certificate, PrivateKey, RootCertStore};
use rustls_pemfile::{Item, certs, rsa_private_keys};
use x509_parser::certificate::X509Certificate;
use x509_parser::prelude::FromDer;
pub use crate::errors::CertsError as Error;
pub fn extract_client_name(cert: Certificate) -> Result<String, Error> {
match X509Certificate::from_der(&cert.0) {
Ok((_, cert)) => {
let subject: String = cert.subject.to_string();
let name_loc: usize = match subject.find("CN=") {
Some(name_loc) => name_loc + 3,
None => {
return Err(Error::ClientCertNoCN { subject });
},
};
let name_end: usize = subject[name_loc..].find(',').map(|c| name_loc + c).unwrap_or(subject.len());
Ok(subject[name_loc..name_end].to_string())
},
Err(err) => Err(Error::ClientCertParseError { err }),
}
}
pub fn load_all(file: impl AsRef<Path>) -> Result<(Vec<Certificate>, Vec<PrivateKey>), Error> {
let file: &Path = file.as_ref();
let handle: fs::File = match fs::File::open(file) {
Ok(handle) => handle,
Err(err) => {
return Err(Error::FileOpenError { what: "PEM", path: file.into(), err });
},
};
let mut reader: io::BufReader<fs::File> = io::BufReader::new(handle);
let mut certs: Vec<Certificate> = vec![];
let mut keys: Vec<PrivateKey> = vec![];
while let Some(item) = rustls_pemfile::read_one(&mut reader).transpose() {
let item: Item = match item {
Ok(item) => item,
Err(err) => {
return Err(Error::FileReadError { what: "PEM", path: file.into(), err });
},
};
match item {
Item::X509Certificate(cert) => certs.push(Certificate(cert)),
Item::ECKey(key) | Item::PKCS8Key(key) | Item::RSAKey(key) => keys.push(PrivateKey(key)),
_ => {
return Err(Error::UnknownItemError { what: "PEM", path: file.into() });
},
}
}
debug!("Loaded PEM file '{}' with {} certificate(s) and {} key(s)", file.display(), certs.len(), keys.len());
Ok((certs, keys))
}
pub fn load_cert(certfile: impl AsRef<Path>) -> Result<Vec<Certificate>, Error> {
let certfile: &Path = certfile.as_ref();
let handle: fs::File = match fs::File::open(certfile) {
Ok(handle) => handle,
Err(err) => {
return Err(Error::FileOpenError { what: "certificate", path: certfile.into(), err });
},
};
let mut reader: io::BufReader<fs::File> = io::BufReader::new(handle);
let certs: Vec<Vec<u8>> = match certs(&mut reader) {
Ok(certs) => certs,
Err(err) => {
return Err(Error::CertFileParseError { path: certfile.into(), err });
},
};
debug!("Found {} certificate(s) in '{}'", certs.len(), certfile.display());
Ok(certs.into_iter().map(Certificate).collect())
}
pub fn load_key(keyfile: impl AsRef<Path>) -> Result<Vec<PrivateKey>, Error> {
let keyfile: &Path = keyfile.as_ref();
let handle: fs::File = match fs::File::open(keyfile) {
Ok(handle) => handle,
Err(err) => {
return Err(Error::FileOpenError { what: "private key", path: keyfile.into(), err });
},
};
let mut reader: io::BufReader<fs::File> = io::BufReader::new(handle);
let keys: Vec<Vec<u8>> = match rsa_private_keys(&mut reader) {
Ok(keys) => keys,
Err(err) => {
return Err(Error::CertFileParseError { path: keyfile.into(), err });
},
};
debug!("Found {} key(s) in '{}'", keys.len(), keyfile.display());
Ok(keys.into_iter().map(PrivateKey).collect())
}
pub fn load_identity(file: impl AsRef<Path>) -> Result<(Vec<Certificate>, PrivateKey), Error> {
let file: &Path = file.as_ref();
let handle: fs::File = match fs::File::open(file) {
Ok(handle) => handle,
Err(err) => {
return Err(Error::FileOpenError { what: "identity", path: file.into(), err });
},
};
let mut reader: io::BufReader<fs::File> = io::BufReader::new(handle);
let mut certs: Vec<Certificate> = vec![];
let mut keys: Vec<PrivateKey> = vec![];
while let Some(item) = rustls_pemfile::read_one(&mut reader).transpose() {
let item: Item = match item {
Ok(item) => item,
Err(err) => {
return Err(Error::FileReadError { what: "identity", path: file.into(), err });
},
};
match item {
Item::X509Certificate(cert) => certs.push(Certificate(cert)),
Item::ECKey(key) | Item::PKCS8Key(key) | Item::RSAKey(key) => keys.push(PrivateKey(key)),
_ => {
return Err(Error::UnknownItemError { what: "identity", path: file.into() });
},
}
}
let key: PrivateKey = if !keys.is_empty() {
keys.swap_remove(0)
} else {
return Err(Error::EmptyKeyFile { path: file.into() });
};
debug!("Loaded client identity file '{}' with {} certificate(s) and {} key(s)", file.display(), certs.len(), 1);
Ok((certs, key))
}
pub fn load_keypair(certfile: impl AsRef<Path>, keyfile: impl AsRef<Path>) -> Result<(Certificate, PrivateKey), Error> {
let certfile: &Path = certfile.as_ref();
let keyfile: &Path = keyfile.as_ref();
let mut certs: Vec<Certificate> = load_cert(certfile)?;
let mut keys: Vec<PrivateKey> = load_key(keyfile)?;
let cert: Certificate = if !certs.is_empty() {
certs.swap_remove(0)
} else {
return Err(Error::EmptyCertFile { path: certfile.into() });
};
let key: PrivateKey = if !keys.is_empty() {
keys.swap_remove(0)
} else {
return Err(Error::EmptyKeyFile { path: keyfile.into() });
};
Ok((cert, key))
}
pub fn load_certstore(storefile: impl AsRef<Path>) -> Result<RootCertStore, Error> {
let storefile: &Path = storefile.as_ref();
let handle: fs::File = match fs::File::open(storefile) {
Ok(handle) => handle,
Err(err) => {
return Err(Error::FileOpenError { what: "client certificate store", path: storefile.into(), err });
},
};
let mut reader: io::BufReader<fs::File> = io::BufReader::new(handle);
let certs: Vec<Vec<u8>> = match certs(&mut reader) {
Ok(certs) => certs,
Err(err) => {
return Err(Error::CertFileParseError { path: storefile.into(), err });
},
};
debug!("Found {} certificate(s) in '{}'", certs.len(), storefile.display());
let mut store: RootCertStore = RootCertStore::empty();
let (added, ignored): (usize, usize) = store.add_parsable_certificates(&certs);
debug!("Created client trust store from '{}' with {} certificates (ignored {})", storefile.display(), added, ignored);
Ok(store)
}