#![forbid(unsafe_code)]
#![deny(missing_docs)]
use core::fmt;
use der::{Decode, Encode};
use der::referenced::OwnedToRef;
use sha2::Digest as _;
#[derive(Debug)]
pub enum Error {
Http(String),
Parse(String),
Verify(String),
Digest(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Http(e) => write!(f, "http: {e}"),
Error::Parse(e) => write!(f, "parse: {e}"),
Error::Verify(e) => write!(f, "verify: {e}"),
Error::Digest(e) => write!(f, "digest: {e}"),
}
}
}
impl std::error::Error for Error {}
pub struct TsaRequest {
pub imprint_alg: &'static str,
pub imprint: Vec<u8>,
pub policy_oid: Option<String>,
pub nonce: [u8; 16],
pub cert_req: bool,
}
#[derive(Clone, Debug)]
pub struct TsaResponse {
pub der: Vec<u8>,
pub gen_time_unix: i64,
pub serial_hex: String,
pub tsa_kid: String,
}
pub fn request_timestamp(url: &str, req: &TsaRequest) -> Result<TsaResponse, Error> {
let der_req = build_timestamp_req_der(req)?;
let mut res = ureq::post(url)
.content_type("application/timestamp-query")
.send(der_req.as_slice())
.map_err(|e| Error::Http(e.to_string()))?;
let der = res
.body_mut()
.read_to_vec()
.map_err(|e| Error::Http(e.to_string()))?;
parse_basic_response(&der)
}
pub fn verify_token(
der: &[u8],
expected_alg: &str,
expected_imprint: &[u8],
expected_nonce: Option<&[u8]>,
trust_anchors: &[Vec<u8>],
) -> Result<TsaResponse, Error> {
let tsr = x509_tsp::TimeStampResp::from_der(der).map_err(|e| Error::Parse(e.to_string()))?;
let status_val = tsr.status.status as u8;
if status_val > 1 {
return Err(Error::Verify(format!("status {} not granted", status_val)));
}
let tst = tsr
.time_stamp_token
.ok_or_else(|| Error::Parse("missing token".into()))?;
let tst_der = tst.to_der().map_err(|e| Error::Parse(e.to_string()))?;
let sd = cms::content_info::ContentInfo::from_der(&tst_der)
.map_err(|e| Error::Parse(format!("cms: {e}")))?;
let (tst_info_der, signer_spki, serial_hex, signer_cert, chain) =
extract_tstinfo_and_signer_spki(&sd).map_err(|e| Error::Parse(e))?;
let tsti = x509_tsp::TstInfo::from_der(&tst_info_der).map_err(|e| Error::Parse(e.to_string()))?;
verify_message_imprint(&tsti, expected_alg, expected_imprint)?;
if let (Some(nonce), Some(exp)) = (tsti.nonce.as_ref(), expected_nonce) {
if nonce.as_bytes() != exp {
return Err(Error::Verify("nonce mismatch".into()));
}
}
verify_eku_and_trust(&signer_cert, &chain, trust_anchors)?;
let signer_der = signer_cert.to_der().map_err(|e| Error::Parse(format!("signer der: {e}")))?;
verify_cms_signed_attrs(&sd, &signer_der, &signer_spki)?;
let tsa_kid = crate::crypto::kid_from_spki_der(&signer_spki);
let gen_time_unix = tsti.gen_time.to_unix_duration().as_secs() as i64;
Ok(TsaResponse {
der: der.to_vec(),
gen_time_unix,
serial_hex,
tsa_kid,
})
}
fn build_timestamp_req_der(req: &TsaRequest) -> Result<Vec<u8>, Error> {
use cms::cert::x509::spki::AlgorithmIdentifier;
use der::asn1::{Int, OctetString};
use der::oid::ObjectIdentifier;
use x509_tsp::{MessageImprint, TimeStampReq, TspVersion};
let alg_oid: ObjectIdentifier = match req.imprint_alg {
"sha512" => const_oid::db::rfc5912::ID_SHA_512,
"shake256-64" => ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.12"),
other => return Err(Error::Digest(other.into())),
};
let alg = AlgorithmIdentifier { oid: alg_oid, parameters: None };
let hashed_message = OctetString::new(req.imprint.clone())
.map_err(|e| Error::Parse(e.to_string()))?;
let imprint = MessageImprint { hash_algorithm: alg, hashed_message };
let tsreq = TimeStampReq {
version: TspVersion::V1,
message_imprint: imprint,
req_policy: req.policy_oid.as_deref().and_then(|s| s.parse().ok()),
nonce: Some(Int::new(&req.nonce).map_err(|e| Error::Parse(e.to_string()))?),
cert_req: req.cert_req,
extensions: None,
};
tsreq
.to_der()
.map_err(|e| Error::Parse(format!("encode req: {e}")))
}
fn parse_basic_response(der: &[u8]) -> Result<TsaResponse, Error> {
let tsr = x509_tsp::TimeStampResp::from_der(der).map_err(|e| Error::Parse(e.to_string()))?;
let status_val = tsr.status.status as u8;
if status_val > 1 {
return Err(Error::Verify(format!("status {} not granted", status_val)));
}
let tst = tsr
.time_stamp_token
.ok_or_else(|| Error::Parse("missing token".into()))?;
let tst_der = tst.to_der().map_err(|e| Error::Parse(e.to_string()))?;
let sd = cms::content_info::ContentInfo::from_der(&tst_der)
.map_err(|e| Error::Parse(format!("cms: {e}")))?;
let (tst_info_der, signer_spki, serial_hex, _signer_cert, _chain) =
extract_tstinfo_and_signer_spki(&sd).map_err(|e| Error::Parse(e))?;
let tsti = x509_tsp::TstInfo::from_der(&tst_info_der).map_err(|e| Error::Parse(e.to_string()))?;
let gen_time_unix = tsti.gen_time.to_unix_duration().as_secs() as i64;
let tsa_kid = crate::crypto::kid_from_spki_der(&signer_spki);
Ok(TsaResponse {
der: der.to_vec(),
gen_time_unix,
serial_hex,
tsa_kid,
})
}
fn extract_tstinfo_and_signer_spki(
ci: &cms::content_info::ContentInfo,
) -> Result<(Vec<u8>, Vec<u8>, String, x509_cert::Certificate, Vec<x509_cert::Certificate>), String> {
use cms::cert::CertificateChoices;
use cms::signed_data::{EncapsulatedContentInfo, SignedData};
let signed: SignedData = SignedData::from_der(&ci.content.to_der().map_err(|e| e.to_string())?)
.map_err(|e| format!("signed_data: {e}"))?;
let EncapsulatedContentInfo { econtent, .. } = signed.encap_content_info;
let tsti_der_any = econtent.ok_or_else(|| "missing econtent".to_string())?;
let mut serial_hex = String::new();
let mut all = Vec::<x509_cert::Certificate>::new();
if let Some(certs) = &signed.certificates {
for i in 0..certs.0.len() {
let ch = certs.0.get(i).ok_or_else(|| "bad certset".to_string())?;
if let CertificateChoices::Certificate(cert) = ch {
serial_hex = hex::encode(cert.tbs_certificate.serial_number.as_bytes());
all.push(cert.clone());
}
}
}
if all.is_empty() {
return Err("no certificates present".into());
}
let signer_cert = all[0].clone();
let signer_spki = signer_cert
.tbs_certificate
.subject_public_key_info
.to_der()
.map_err(|e| e.to_string())?;
Ok((tsti_der_any.value().to_vec(), signer_spki, serial_hex, signer_cert, all))
}
fn verify_message_imprint(
tsti: &x509_tsp::TstInfo,
expected_alg: &str,
expected_imprint: &[u8],
) -> Result<(), Error> {
let oid = tsti.message_imprint.hash_algorithm.oid;
let ok = match expected_alg {
"sha512" => oid == const_oid::db::rfc5912::ID_SHA_512,
"shake256-64" => oid == const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.12"),
other => return Err(Error::Digest(other.into())),
};
if !ok {
return Err(Error::Verify("hash alg mismatch".into()));
}
let got = tsti.message_imprint.hashed_message.as_bytes();
if got != expected_imprint {
return Err(Error::Verify("messageImprint mismatch".into()));
}
Ok(())
}
fn verify_cms_signed_attrs(
ci: &cms::content_info::ContentInfo,
_signer_cert_der: &[u8],
spki_der: &[u8],
) -> Result<(), Error> {
let signed_data: cms::signed_data::SignedData = cms::signed_data::SignedData::from_der(
&ci.content
.to_der()
.map_err(|e| Error::Parse(format!("content der: {e}")))?,
)
.map_err(|e| Error::Parse(format!("signed_data: {e}")))?;
let si = signed_data
.signer_infos
.0
.get(0)
.ok_or_else(|| Error::Parse("no SignerInfo".into()))?;
let attrs = si
.signed_attrs
.as_ref()
.ok_or_else(|| Error::Parse("missing signedAttrs".into()))?;
let attrs_der = attrs
.to_der()
.map_err(|e| Error::Parse(format!("attrs der: {e}")))?;
let sig = si.signature.as_bytes();
let sig_oid = si.signature_algorithm.oid;
let dig_oid = si.digest_alg.oid;
use pkcs8::spki::SubjectPublicKeyInfoRef;
use ecdsa::signature::Verifier as _;
let spki = SubjectPublicKeyInfoRef::try_from(spki_der).map_err(|e| Error::Parse(e.to_string()))?;
let spki_alg = spki.algorithm.oid;
let spki_key_bytes = spki.subject_public_key.raw_bytes();
match (spki_alg, sig_oid, dig_oid) {
(o, s, d)
if o == const_oid::db::rfc5912::ID_EC_PUBLIC_KEY
&& s == const_oid::db::rfc5912::ECDSA_WITH_SHA_256
&& d == const_oid::db::rfc5912::ID_SHA_256 =>
{
let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(spki_key_bytes)
.map_err(|e| Error::Verify(format!("p256 sec1: {e}")))?;
let sig = p256::ecdsa::Signature::from_der(sig).map_err(|e| Error::Verify(format!("p256 sig: {e}")))?;
vk.verify(&attrs_der, &sig).map_err(|_| Error::Verify("ecdsa p256 sha256".into()))
}
(o, s, d)
if o == const_oid::db::rfc5912::ID_EC_PUBLIC_KEY
&& s == const_oid::db::rfc5912::ECDSA_WITH_SHA_384
&& d == const_oid::db::rfc5912::ID_SHA_384 =>
{
let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(spki_key_bytes)
.map_err(|e| Error::Verify(format!("p384 sec1: {e}")))?;
let sig = p384::ecdsa::Signature::from_der(sig).map_err(|e| Error::Verify(format!("p384 sig: {e}")))?;
vk.verify(&attrs_der, &sig).map_err(|_| Error::Verify("ecdsa p384 sha384".into()))
}
(o, s, d)
if o == const_oid::db::rfc5912::RSA_ENCRYPTION
&& s == const_oid::db::rfc5912::ID_RSASSA_PSS =>
{
use rsa::pss::{Signature as RsaPssSignature, VerifyingKey};
use pkcs8::DecodePublicKey;
let pk = rsa::RsaPublicKey::from_public_key_der(spki_der)
.map_err(|e| Error::Verify(format!("rsa spki: {e}")))?;
let rsig = RsaPssSignature::try_from(sig).map_err(|_| Error::Verify("rsa-pss sig parse".into()))?;
let ok = if d == const_oid::db::rfc5912::ID_SHA_512 {
VerifyingKey::<sha2::Sha512>::new_with_salt_len(pk.clone(), sha2::Sha512::output_size())
.verify(&attrs_der, &rsig)
.is_ok()
} else if d == const_oid::db::rfc5912::ID_SHA_384 {
VerifyingKey::<sha2::Sha384>::new_with_salt_len(pk.clone(), sha2::Sha384::output_size())
.verify(&attrs_der, &rsig)
.is_ok()
} else if d == const_oid::db::rfc5912::ID_SHA_256 {
VerifyingKey::<sha2::Sha256>::new_with_salt_len(pk.clone(), sha2::Sha256::output_size())
.verify(&attrs_der, &rsig)
.is_ok()
} else {
VerifyingKey::<sha2::Sha512>::new_with_salt_len(pk.clone(), sha2::Sha512::output_size())
.verify(&attrs_der, &rsig)
.is_ok()
|| VerifyingKey::<sha2::Sha384>::new_with_salt_len(pk.clone(), sha2::Sha384::output_size())
.verify(&attrs_der, &rsig)
.is_ok()
|| VerifyingKey::<sha2::Sha256>::new_with_salt_len(pk.clone(), sha2::Sha256::output_size())
.verify(&attrs_der, &rsig)
.is_ok()
};
if ok { Ok(()) } else { Err(Error::Verify("rsa-pss verify failed".into())) }
}
(o, s, d) if o == const_oid::db::rfc5912::RSA_ENCRYPTION && s == const_oid::db::rfc5912::RSA_ENCRYPTION => {
use rsa::pkcs1v15::{Signature as RsaSignature, VerifyingKey};
use pkcs8::DecodePublicKey;
let pk = rsa::RsaPublicKey::from_public_key_der(spki_der)
.map_err(|e| Error::Verify(format!("rsa spki: {e}")))?;
let rsig = RsaSignature::try_from(sig).map_err(|_| Error::Verify("rsa sig parse".into()))?;
fn try_rsa<D>(pk: &rsa::RsaPublicKey, msg: &[u8], sig: &RsaSignature) -> bool
where
D: sha2::digest::Digest + const_oid::AssociatedOid,
{
let vk = VerifyingKey::<D>::new_unprefixed(pk.clone());
vk.verify(msg, sig).is_ok()
}
let ok = if d == const_oid::db::rfc5912::ID_SHA_512 {
try_rsa::<sha2::Sha512>(&pk, &attrs_der, &rsig)
} else if d == const_oid::db::rfc5912::ID_SHA_384 {
try_rsa::<sha2::Sha384>(&pk, &attrs_der, &rsig)
} else if d == const_oid::db::rfc5912::ID_SHA_256 {
try_rsa::<sha2::Sha256>(&pk, &attrs_der, &rsig)
} else {
try_rsa::<sha2::Sha512>(&pk, &attrs_der, &rsig)
|| try_rsa::<sha2::Sha384>(&pk, &attrs_der, &rsig)
|| try_rsa::<sha2::Sha256>(&pk, &attrs_der, &rsig)
};
if ok { Ok(()) } else { Err(Error::Verify("rsa pkcs1 verify failed".into())) }
}
_ => Err(Error::Verify("unsupported CMS algorithm".into())),
}
}
fn verify_eku_and_trust(
signer: &x509_cert::Certificate,
chain: &[x509_cert::Certificate],
trust: &[Vec<u8>],
) -> Result<(), Error> {
use const_oid::db::rfc5280::ID_KP_TIME_STAMPING;
use x509_cert::ext::pkix::ExtendedKeyUsage;
use const_oid::AssociatedOid as _;
let mut has_eku = false;
if let Some(exts) = signer.tbs_certificate.extensions.as_ref() {
for ext in exts {
if ext.extn_id == ExtendedKeyUsage::OID {
let eku = ExtendedKeyUsage::from_der(ext.extn_value.as_bytes())
.map_err(|e| Error::Parse(format!("eku: {e}")))?;
if eku.0.iter().any(|oid| *oid == ID_KP_TIME_STAMPING) {
has_eku = true;
break;
}
}
}
}
if !has_eku {
for c in chain {
if let Some(exts) = c.tbs_certificate.extensions.as_ref() {
for ext in exts {
if ext.extn_id == ExtendedKeyUsage::OID {
let eku = ExtendedKeyUsage::from_der(ext.extn_value.as_bytes())
.map_err(|e| Error::Parse(format!("eku: {e}")))?;
if eku.0.iter().any(|oid| *oid == ID_KP_TIME_STAMPING) {
has_eku = true;
break;
}
}
}
}
if has_eku { break; }
}
}
if !has_eku {
return Err(Error::Verify("timeStamping EKU not found on signer or issuing CA".into()));
}
let mut trusted_kids = std::collections::BTreeSet::new();
let mut trust_certs: Vec<x509_cert::Certificate> = Vec::new();
for blob in trust {
let kid = if let Ok(cert) = x509_cert::Certificate::from_der(blob) {
let spki = cert
.tbs_certificate
.subject_public_key_info
.to_der()
.map_err(|e| Error::Parse(format!("spki: {e}")))?;
crate::crypto::kid_from_spki_der(&spki)
} else {
crate::crypto::kid_from_spki_der(blob)
};
trusted_kids.insert(kid);
if let Ok(cert) = x509_cert::Certificate::from_der(blob) {
trust_certs.push(cert);
}
}
let signer_spki = signer
.tbs_certificate
.subject_public_key_info
.to_der()
.map_err(|e| Error::Parse(format!("spki: {e}")))?;
let signer_kid = crate::crypto::kid_from_spki_der(&signer_spki);
if trusted_kids.contains(&signer_kid) {
return Ok(());
}
for c in chain {
let spki = c
.tbs_certificate
.subject_public_key_info
.to_der()
.map_err(|e| Error::Parse(format!("spki: {e}")))?;
let kid = crate::crypto::kid_from_spki_der(&spki);
if trusted_kids.contains(&kid) {
return Ok(());
}
}
if let Some(()) = verify_signer_cert_with_trust(&signer, &trust_certs).ok() {
return Ok(());
}
Err(Error::Verify("TSA chain not anchored in provided trust".into()))
}
fn verify_signer_cert_with_trust(
signer: &x509_cert::Certificate,
trust_certs: &[x509_cert::Certificate],
) -> Result<(), Error> {
use const_oid::db::rfc5912 as oids;
let sig_oid = signer.signature_algorithm.oid;
let tbs_der = signer
.tbs_certificate
.to_der()
.map_err(|e| Error::Parse(format!("tbs der: {e}")))?;
for issuer in trust_certs {
let spki = &issuer.tbs_certificate.subject_public_key_info;
if spki.algorithm.oid != oids::ID_EC_PUBLIC_KEY {
continue;
}
let curve = spki
.algorithm
.owned_to_ref()
.parameters_oid()
.ok();
let pk_bytes = spki
.subject_public_key
.raw_bytes()
.to_vec();
if sig_oid == oids::ECDSA_WITH_SHA_256 && curve == Some(oids::SECP_256_R_1) {
use ecdsa::signature::DigestVerifier;
let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(&pk_bytes)
.map_err(|_| Error::Verify("invalid issuer P-256 key".into()))?;
let sig_der = signer
.signature
.as_bytes()
.ok_or_else(|| Error::Verify("missing signature bytes".into()))?;
let sig = p256::ecdsa::Signature::from_der(sig_der)
.map_err(|_| Error::Verify("invalid ECDSA P-256 signature".into()))?;
let ok = vk
.verify_digest(sha2::Sha256::new().chain_update(&tbs_der), &sig)
.is_ok();
if ok {
return Ok(());
}
}
if sig_oid == oids::ECDSA_WITH_SHA_384 && curve == Some(const_oid::db::rfc5912::SECP_384_R_1) {
use ecdsa::signature::DigestVerifier;
let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(&pk_bytes)
.map_err(|_| Error::Verify("invalid issuer P-384 key".into()))?;
let sig_der = signer
.signature
.as_bytes()
.ok_or_else(|| Error::Verify("missing signature bytes".into()))?;
let sig = p384::ecdsa::Signature::from_der(sig_der)
.map_err(|_| Error::Verify("invalid ECDSA P-384 signature".into()))?;
let ok = vk
.verify_digest(sha2::Sha384::new().chain_update(&tbs_der), &sig)
.is_ok();
if ok {
return Ok(());
}
}
}
Err(Error::Verify("could not validate signer cert against trust (ECDSA)".into()))
}