use crate::certificate::{
Certificate, CertificateError, FoundRoot, MAX_CERTIFICATE_LEN, Trust, file_roots,
parse_fingerprint, verify_imported,
};
use crate::gzip::{self, GzipError};
use crate::signing::KeyError;
use crate::store::StoreError;
use crate::xml;
use std::fmt;
use x509_cert::der::{Decode, DecodePem, Encode};
pub const MAX_IMPORT_LEN: usize = 4 * 1024 * 1024;
pub const MAX_IMPORT_ITEMS: usize = 128;
pub const MAX_PASSWORD_LEN: usize = 1024;
pub const MAX_ITERATIONS: u32 = 1_000_000;
#[derive(Debug)]
pub enum ImportError {
Limit,
Malformed,
Gzip(GzipError),
Certificate(CertificateError),
UntrustedRoot {
subject: String,
fingerprint: String,
},
RootMismatch {
given: String,
},
Key(KeyError),
Authentication,
UnsupportedProtection,
Store(StoreError),
}
impl fmt::Display for ImportError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Limit => f.write_str("the certificate import exceeds a limit"),
Self::Malformed => f.write_str("the certificate import is malformed"),
Self::Gzip(e) => e.fmt(f),
Self::Certificate(e) => e.fmt(f),
Self::UntrustedRoot {
subject,
fingerprint,
} => write!(
f,
"the certificates chain to a root that is not trusted yet: {subject}, SHA-256 \
{fingerprint}"
),
Self::RootMismatch { given } if given.is_empty() => {
f.write_str("the root to trust was not given as a SHA-256 fingerprint")
}
Self::RootMismatch { given } => write!(
f,
"the root to trust, SHA-256 {given}, is not one this file's certificates chain to"
),
Self::Key(e) => e.fmt(f),
Self::Authentication => f.write_str("the PKCS#12 MAC could not be authenticated"),
Self::UnsupportedProtection => {
f.write_str("unsupported certificate import protection or bag type")
}
Self::Store(e) => e.fmt(f),
}
}
}
impl std::error::Error for ImportError {}
impl From<CertificateError> for ImportError {
fn from(e: CertificateError) -> Self {
Self::Certificate(e)
}
}
impl From<KeyError> for ImportError {
fn from(e: KeyError) -> Self {
Self::Key(e)
}
}
impl From<StoreError> for ImportError {
fn from(e: StoreError) -> Self {
Self::Store(e)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ImportedCertificate {
pub id: String,
pub has_key: bool,
pub attached_request: bool,
pub already_here: bool,
}
pub(crate) struct Checked {
pub certificates: Vec<Certificate>,
pub root: Option<FoundRoot>,
}
pub(crate) fn check_certificates(
users: &[Vec<u8>],
authorities: &[Vec<u8>],
trust: &Trust,
accept_root: Option<&str>,
) -> Result<Checked, ImportError> {
if users.is_empty() {
return Err(ImportError::Malformed);
}
let found = file_roots(users, authorities, trust);
let root = match accept_root {
None => None,
Some(given) => {
let canonical = parse_fingerprint(given);
let Some(f) = found
.iter()
.find(|f| canonical.as_deref() == Some(f.fingerprint.as_str()))
else {
return Err(ImportError::RootMismatch {
given: canonical.unwrap_or_default(),
});
};
Some(f.clone())
}
};
let under = |trust: &Trust| -> Result<Vec<Certificate>, ImportError> {
for authority in authorities {
verify_imported(authority, trust, authorities, true)?;
}
users
.iter()
.map(|der| {
let cert = Certificate::from_pem_or_der(der)?;
cert.verify(trust, authorities)?;
Ok(cert)
})
.collect()
};
match under(&trust.with_found(root.as_slice())) {
Ok(certificates) => Ok(Checked { certificates, root }),
Err(e) => {
if let Some(f) = found.iter().find(|f| Some(*f) != root.as_ref())
&& under(&trust.with_found(&found)).is_ok()
{
return Err(ImportError::UntrustedRoot {
subject: f.subject.clone(),
fingerprint: f.fingerprint.clone(),
});
}
Err(e)
}
}
}
pub(crate) fn tq6(
bytes: &[u8],
trust: &Trust,
accept_root: Option<&str>,
) -> Result<Checked, ImportError> {
if bytes.len() > MAX_IMPORT_LEN {
return Err(ImportError::Limit);
}
let plain = if gzip::is_gzip(bytes) {
gzip::decompress(bytes, MAX_IMPORT_LEN).map_err(|e| match e {
GzipError::TooLarge => ImportError::Limit,
e => ImportError::Gzip(e),
})?
} else {
zeroize::Zeroizing::new(bytes.to_vec())
};
let root = xml::parse(std::str::from_utf8(&plain).map_err(|_| ImportError::Malformed)?)
.map_err(|_| ImportError::Malformed)?;
if root.name != "tqsldata"
|| root.children.len() != 1
|| root.children[0].name != "tqslcerts"
|| !root.text().trim().is_empty()
|| !root.children[0].text().trim().is_empty()
{
return Err(ImportError::Malformed);
}
let mut users = Vec::new();
let mut authorities = Vec::new();
for entry in &root.children[0].children {
if users.len() + authorities.len() >= MAX_IMPORT_ITEMS {
return Err(ImportError::Limit);
}
if !entry.children.is_empty() {
return Err(ImportError::Malformed);
}
let pem = entry.text();
if pem.len() > MAX_CERTIFICATE_LEN {
return Err(ImportError::Limit);
}
let der = x509_cert::Certificate::from_pem(pem.trim().as_bytes())
.and_then(|c| c.to_der())
.map_err(|_| ImportError::Malformed)?;
match entry.name {
"usercert" => users.push(der),
"rootcert" | "cacert" => authorities.push(der),
_ => return Err(ImportError::Malformed),
}
}
check_certificates(&users, &authorities, trust, accept_root)
}
pub(crate) fn is_authority(der: &[u8]) -> Result<bool, ImportError> {
use x509_cert::ext::pkix::BasicConstraints;
let cert = x509_cert::Certificate::from_der(der).map_err(|_| ImportError::Malformed)?;
cert.tbs_certificate
.get::<BasicConstraints>()
.map(|v| v.is_some_and(|(_, bc)| bc.ca))
.map_err(|_| ImportError::Malformed)
}