use crate::certificate::{
Certificate, CertificateError, FoundRoot, MAX_CERTIFICATE_LEN, Trust, file_roots,
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, 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(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ImportRefusal {
Certificate,
CertificateLimit,
PendingRequest,
}
impl fmt::Display for ImportRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Certificate => "the certificate does not chain to a trusted LoTW root",
Self::CertificateLimit => "the store exceeds its limit: certificates",
Self::PendingRequest => "a request still waiting for ARRL",
})
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ImportError {
Limit,
Malformed,
Gzip(GzipError),
Certificate(CertificateError),
UntrustedRoot {
subject: String,
fingerprint: String,
},
RootMismatch {
given: String,
},
Key(KeyError),
Authentication,
UnsupportedProtection,
Conflict,
Refused {
index: usize,
reason: ImportRefusal,
},
Backup(crate::tbk::TbkError),
KeyAt {
index: usize,
error: KeyError,
},
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::Conflict => {
f.write_str("the store changed after inspection; inspect the import again")
}
Self::Backup(e) => e.fmt(f),
Self::Refused { index, reason } => write!(f, "import entry {index}: {reason}"),
Self::KeyAt { index, error } => write!(f, "import key {index}: {error}"),
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,
}
type InspectedCertificates = (Vec<(Certificate, Option<CertificateError>)>, Vec<FoundRoot>);
pub(crate) fn inspect_certificates(
users: &[Vec<u8>],
authorities: &[Vec<u8>],
trust: &Trust,
) -> Result<InspectedCertificates, ImportError> {
if users.is_empty() {
return Err(ImportError::Malformed);
}
let roots = file_roots(users, authorities, trust);
let trust = trust.with_found(&roots);
let authority_error = authorities
.iter()
.find_map(|a| verify_imported(a, &trust, authorities, true).err());
let certificates = users
.iter()
.map(|der| {
let certificate = Certificate::from_pem_or_der(der)?;
let refusal = authority_error
.clone()
.or_else(|| certificate.verify(&trust, authorities).err());
Ok((certificate, refusal))
})
.collect::<Result<_, ImportError>>()?;
Ok((certificates, roots))
}
type CertificateContents = (Vec<Vec<u8>>, Vec<Vec<u8>>);
pub(crate) fn tq6_contents(bytes: &[u8]) -> Result<CertificateContents, 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 = crate::certificate::x509_from_pem(pem.trim().as_bytes())
.map_err(|_| ImportError::Malformed)?
.to_der()
.map_err(|_| ImportError::Malformed)?;
match entry.name {
"usercert" => users.push(der),
"rootcert" | "cacert" => authorities.push(der),
_ => return Err(ImportError::Malformed),
}
}
Ok((users, authorities))
}
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)
}