pub(crate) mod ocsp;
mod profile;
pub(crate) use ocsp::online::{
build_request as build_ocsp_request, evaluate as evaluate_ocsp_online,
responder_url as ocsp_responder_url, OnlineVerdict as OnlineOcspVerdict,
};
pub(crate) use ocsp::{
evaluate_verified as evaluate_ocsp_verified, OcspStatus, MAX_OCSP_RESPONSE_BYTES,
};
mod timestamp;
#[cfg(test)]
pub(crate) mod timestamp_fixture;
use std::collections::{HashMap, HashSet};
pub(crate) use timestamp::{
describe_timestamp_token, inspect_timestamp_token, token_from_timestamp_response,
verify_timestamp_token, TimestampResult, TokenDescription,
};
use const_oid::ObjectIdentifier;
use der::{Decode, Encode};
use ecdsa::signature::hazmat::PrehashVerifier;
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::signature::Verifier as _;
use sha2::{Digest, Sha256, Sha384, Sha512};
use thiserror::Error;
use time::OffsetDateTime;
use x509_cert::ext::pkix::{BasicConstraints, CertificatePolicies, ExtendedKeyUsage};
use x509_cert::Certificate;
pub const OID_C2PA_CLAIM_SIGNING: &str = "1.3.6.1.4.1.62558.2.1";
pub const OID_EMAIL_PROTECTION: &str = "1.3.6.1.5.5.7.3.4";
pub const OID_ADOBE_DOCUMENT_SIGNING: &str = "1.2.840.113583.1.1.5";
pub const OID_IETF_DOCUMENT_SIGNING: &str = "1.3.6.1.5.5.7.3.36";
pub const OID_KP_TIME_STAMPING: &str = "1.3.6.1.5.5.7.3.8";
pub const OID_KP_OCSP_SIGNING: &str = "1.3.6.1.5.5.7.3.9";
pub const OID_MICROSOFT_C2PA: &str = "1.3.6.1.4.1.311.76.59.1.9";
const OID_EXT_EKU: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.29.37");
const OID_EXT_BASIC_CONSTRAINTS: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.29.19");
const OID_AT_COMMON_NAME: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.4.3");
const OID_EXT_KEY_USAGE: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.29.15");
const OID_ANY_EKU: &str = "2.5.29.37.0";
const OID_EC_PUBLIC_KEY: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
const OID_RSA_ENCRYPTION: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
const OID_ED25519: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");
const OID_CURVE_P256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
const OID_CURVE_P384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.34");
const OID_CURVE_P521: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.35");
const OID_ECDSA_SHA256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
const OID_ECDSA_SHA384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.3");
const OID_ECDSA_SHA512: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.4");
const OID_RSA_SHA256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11");
const OID_RSA_SHA384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.12");
const OID_RSA_SHA512: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.13");
const MAX_CHAIN_DEPTH: usize = 20;
const MAX_PATH_NODES: usize = 256;
#[derive(Debug, Error)]
pub enum TrustError {
#[error("no certificates found in PEM input")]
NoCertificates,
#[error("failed to decode certificate: {0}")]
Decode(String),
}
#[derive(Clone, Copy)]
enum SigHash {
Sha256,
Sha384,
Sha512,
}
fn digest_bytes(hash: SigHash, msg: &[u8]) -> Vec<u8> {
match hash {
SigHash::Sha256 => Sha256::digest(msg).to_vec(),
SigHash::Sha384 => Sha384::digest(msg).to_vec(),
SigHash::Sha512 => Sha512::digest(msg).to_vec(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnchorPurpose {
ClaimSigning,
TimeStamping,
CawgIdentity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CawgTrustSource {
SmimeInterim,
EncypherVerifiedOrganizations,
CallerSupplied,
}
impl CawgTrustSource {
pub fn label(self) -> &'static str {
match self {
Self::SmimeInterim => "smime_interim",
Self::EncypherVerifiedOrganizations => "encypher_verified_organizations",
Self::CallerSupplied => "caller_supplied",
}
}
pub fn interim(self) -> bool {
matches!(self, Self::SmimeInterim)
}
}
#[derive(Debug, Clone)]
pub struct TrustAnchor {
pub certificate: Vec<u8>,
pub purpose: AnchorPurpose,
pub not_before: Option<OffsetDateTime>,
pub not_after: Option<OffsetDateTime>,
pub cawg_source: CawgTrustSource,
}
impl TrustAnchor {
pub fn new(certificate: Vec<u8>, purpose: AnchorPurpose) -> Self {
Self {
certificate,
purpose,
not_before: None,
not_after: None,
cawg_source: CawgTrustSource::SmimeInterim,
}
}
pub fn active_at(&self, at: OffsetDateTime) -> bool {
self.not_before.is_none_or(|start| at >= start)
&& self.not_after.is_none_or(|end| at <= end)
}
}
#[derive(Debug, Clone, Default)]
pub struct TrustList {
pub anchors: Vec<TrustAnchor>,
}
impl TrustList {
pub fn from_pem(pem: &str) -> Result<Self, TrustError> {
Self::from_pem_for(AnchorPurpose::ClaimSigning, pem)
}
pub fn from_pem_for(purpose: AnchorPurpose, pem: &str) -> Result<Self, TrustError> {
if !pem.contains("-----BEGIN CERTIFICATE-----") {
return Err(TrustError::NoCertificates);
}
let certs = Certificate::load_pem_chain(pem.as_bytes())
.map_err(|e| TrustError::Decode(e.to_string()))?;
if certs.is_empty() {
return Err(TrustError::NoCertificates);
}
let mut anchors = Vec::with_capacity(certs.len());
for cert in &certs {
let der = cert
.to_der()
.map_err(|e| TrustError::Decode(e.to_string()))?;
anchors.push(TrustAnchor::new(der, purpose));
}
Ok(Self { anchors })
}
pub fn from_certificates(
purpose: AnchorPurpose,
certificates: impl IntoIterator<Item = Vec<u8>>,
) -> Self {
Self {
anchors: certificates
.into_iter()
.map(|der| TrustAnchor::new(der, purpose))
.collect(),
}
}
pub fn with_bounds(
mut self,
not_before: Option<OffsetDateTime>,
not_after: Option<OffsetDateTime>,
) -> Self {
for anchor in &mut self.anchors {
anchor.not_before = not_before;
anchor.not_after = not_after;
}
self
}
pub fn with_cawg_source(mut self, source: CawgTrustSource) -> Self {
for anchor in &mut self.anchors {
anchor.cawg_source = source;
}
self
}
pub fn find_certificate(&self, der: &[u8]) -> Option<&TrustAnchor> {
self.anchors.iter().find(|anchor| anchor.certificate == der)
}
pub fn contains_certificate(&self, der: &[u8]) -> bool {
self.find_certificate(der).is_some()
}
pub fn certificates(&self) -> impl Iterator<Item = &[u8]> {
self.anchors
.iter()
.map(|anchor| anchor.certificate.as_slice())
}
pub fn anchor_subjects(&self) -> Vec<String> {
self.certificates()
.filter_map(|der| {
let cert = Certificate::from_der(der).ok()?;
common_name(&cert)
})
.collect()
}
fn active_anchors(
&self,
purpose: AnchorPurpose,
at: OffsetDateTime,
) -> Vec<(usize, &TrustAnchor)> {
self.anchors
.iter()
.enumerate()
.filter(|(_, anchor)| anchor.purpose == purpose && anchor.active_at(at))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct EkuPolicy {
pub allowed_oids: Vec<String>,
}
impl Default for EkuPolicy {
fn default() -> Self {
Self {
allowed_oids: vec![
OID_C2PA_CLAIM_SIGNING.to_string(),
OID_ADOBE_DOCUMENT_SIGNING.to_string(),
OID_IETF_DOCUMENT_SIGNING.to_string(),
OID_EMAIL_PROTECTION.to_string(),
OID_MICROSOFT_C2PA.to_string(),
],
}
}
}
impl EkuPolicy {
pub fn cert_has_required_eku(&self, cert_der: &[u8]) -> bool {
if self.allowed_oids.is_empty() {
return true;
}
let Ok(cert) = Certificate::from_der(cert_der) else {
return false;
};
let Some(ekus) = certificate_eku_oids(&cert) else {
return false;
};
ekus.iter()
.any(|oid| self.allowed_oids.iter().any(|allowed| allowed == oid))
}
}
fn certificate_eku_oids(cert: &Certificate) -> Option<Vec<String>> {
let exts = cert.tbs_certificate.extensions.as_ref()?;
let ext = exts.iter().find(|e| e.extn_id == OID_EXT_EKU)?;
let eku = ExtendedKeyUsage::from_der(ext.extn_value.as_bytes()).ok()?;
Some(eku.0.iter().map(|oid| oid.to_string()).collect())
}
pub fn certificate_eku_oids_der(cert_der: &[u8]) -> Option<Vec<String>> {
let cert = Certificate::from_der(cert_der).ok()?;
certificate_eku_oids(&cert)
}
pub fn certificate_policy_oids_der(cert_der: &[u8]) -> Option<Vec<String>> {
let cert = Certificate::from_der(cert_der).ok()?;
let exts = cert.tbs_certificate.extensions.as_ref()?;
let oid = ObjectIdentifier::new_unwrap("2.5.29.32");
let ext = exts.iter().find(|extension| extension.extn_id == oid)?;
let policies = CertificatePolicies::from_der(ext.extn_value.as_bytes()).ok()?;
Some(
policies
.0
.iter()
.map(|policy| policy.policy_identifier.to_string())
.collect(),
)
}
pub fn certificate_valid_at(cert_der: &[u8], at: OffsetDateTime) -> bool {
Certificate::from_der(cert_der)
.map(|cert| valid_at(&cert, at))
.unwrap_or(false)
}
#[derive(Debug, Clone)]
pub struct ChainResult {
pub trusted: bool,
pub chain_validity_ok: bool,
pub leaf_acceptable: bool,
pub reason: Option<String>,
pub validated_at: OffsetDateTime,
pub anchor: Option<usize>,
}
impl ChainResult {
fn untrusted(reason: impl Into<String>, at: OffsetDateTime) -> Self {
Self {
trusted: false,
chain_validity_ok: true,
leaf_acceptable: true,
reason: Some(reason.into()),
validated_at: at,
anchor: None,
}
}
pub fn terminating_anchor<'a>(&self, trust: &'a TrustList) -> Option<&'a TrustAnchor> {
trust.anchors.get(self.anchor?)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum PathOutcome {
NoPath,
Rejected(&'static str),
OutsideValidity,
Trusted,
}
pub fn validate_chain(
leaf_der: &[u8],
intermediates_der: &[Vec<u8>],
trust: &TrustList,
purpose: AnchorPurpose,
validation_time: Option<OffsetDateTime>,
) -> ChainResult {
let at = validation_time.unwrap_or_else(OffsetDateTime::now_utc);
let leaf = match Certificate::from_der(leaf_der) {
Ok(c) => c,
Err(e) => return ChainResult::untrusted(format!("invalid leaf certificate: {e}"), at),
};
let leaf_acceptable = leaf_is_acceptable_claim_signer(&leaf);
let mut anchor_indices: HashMap<String, usize> = HashMap::new();
let mut candidates: Vec<Certificate> = Vec::new();
for der in intermediates_der {
if let Ok(c) = Certificate::from_der(der) {
candidates.push(c);
}
}
for (index, anchor) in trust.active_anchors(purpose, at) {
if let Ok(c) = Certificate::from_der(&anchor.certificate) {
anchor_indices
.entry(fingerprint_hex(&anchor.certificate))
.or_insert(index);
candidates.push(c);
}
}
let mut path = vec![leaf];
let mut seen: HashSet<String> = HashSet::from([fingerprint_hex(leaf_der)]);
let mut budget = MAX_PATH_NODES;
let mut anchor = None;
let outcome = extend_path(
&mut path,
&candidates,
&anchor_indices,
at,
&mut seen,
&mut budget,
&mut anchor,
);
let (trusted, chain_validity_ok, reason) = match outcome {
PathOutcome::Trusted => (true, true, None),
PathOutcome::OutsideValidity => (
false,
false,
Some("a certificate in the chain was outside its validity window".to_string()),
),
PathOutcome::Rejected(reason) => (false, true, Some(reason.to_string())),
PathOutcome::NoPath => (
false,
valid_at(path.first().expect("path holds the leaf"), at),
Some("certificate does not chain to a trusted anchor".to_string()),
),
};
ChainResult {
trusted,
chain_validity_ok,
leaf_acceptable,
reason,
validated_at: at,
anchor: trusted.then_some(anchor).flatten(),
}
}
fn extend_path(
path: &mut Vec<Certificate>,
candidates: &[Certificate],
anchor_indices: &HashMap<String, usize>,
at: OffsetDateTime,
seen: &mut HashSet<String>,
budget: &mut usize,
anchor: &mut Option<usize>,
) -> PathOutcome {
let current = path.last().expect("path is never empty").clone();
let Ok(current_der) = current.to_der() else {
return PathOutcome::NoPath;
};
if let Some(&index) = anchor_indices.get(&fingerprint_hex(¤t_der)) {
let outcome = evaluate_path(path, at);
if outcome == PathOutcome::Trusted {
*anchor = Some(index);
}
return outcome;
}
if path.len() >= MAX_CHAIN_DEPTH
|| current.tbs_certificate.subject == current.tbs_certificate.issuer
{
return PathOutcome::NoPath;
}
let mut best = PathOutcome::NoPath;
for candidate in candidates {
if *budget == 0 {
break;
}
if candidate.tbs_certificate.subject != current.tbs_certificate.issuer
|| !verify_signature(¤t, candidate)
{
continue;
}
let Ok(der) = candidate.to_der() else {
continue;
};
let fingerprint = fingerprint_hex(&der);
if !seen.insert(fingerprint.clone()) {
continue;
}
*budget -= 1;
path.push(candidate.clone());
let outcome = extend_path(path, candidates, anchor_indices, at, seen, budget, anchor);
path.pop();
seen.remove(&fingerprint);
best = best.max(outcome);
if best == PathOutcome::Trusted {
break;
}
}
best
}
fn evaluate_path(path: &[Certificate], at: OffsetDateTime) -> PathOutcome {
if let Some(reason) = profile::path_violation(path) {
return PathOutcome::Rejected(reason);
}
if path.iter().any(|cert| !valid_at(cert, at)) {
return PathOutcome::OutsideValidity;
}
PathOutcome::Trusted
}
pub fn resolve_issuer<'a>(
leaf_der: &[u8],
candidates: impl IntoIterator<Item = &'a [u8]>,
) -> Option<&'a [u8]> {
let leaf = Certificate::from_der(leaf_der).ok()?;
let leaf_issuer_dn = leaf.tbs_certificate.issuer.to_der().ok()?;
for cand_der in candidates {
let Ok(cand) = Certificate::from_der(cand_der) else {
continue;
};
let Ok(cand_subject_dn) = cand.tbs_certificate.subject.to_der() else {
continue;
};
if cand_subject_dn == leaf_issuer_dn && verify_signature(&leaf, &cand) {
return Some(cand_der);
}
}
None
}
#[derive(Debug, Clone, Default)]
pub struct RevocationDenylist {
pub serials: HashSet<String>,
pub fingerprints: HashSet<String>,
}
impl RevocationDenylist {
pub fn new(
serials: impl IntoIterator<Item = String>,
fingerprints: impl IntoIterator<Item = String>,
) -> Self {
Self {
serials: normalize_tokens(serials),
fingerprints: normalize_tokens(fingerprints),
}
}
pub fn is_revoked(&self, cert_der: &[u8]) -> bool {
if !self.fingerprints.is_empty() && self.fingerprints.contains(&fingerprint_hex(cert_der)) {
return true;
}
if !self.serials.is_empty() {
if let Ok(cert) = Certificate::from_der(cert_der) {
let serial = serial_hex(cert.tbs_certificate.serial_number.as_bytes());
if self.serials.contains(&serial) {
return true;
}
}
}
false
}
}
fn normalize_tokens(values: impl IntoIterator<Item = String>) -> HashSet<String> {
values
.into_iter()
.filter_map(|v| {
let t = v.trim().to_ascii_lowercase();
if t.is_empty() {
None
} else {
Some(t)
}
})
.collect()
}
fn fingerprint_hex(der: &[u8]) -> String {
hex::encode(Sha256::digest(der))
}
fn serial_hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{b:02x}"));
}
let trimmed = s.trim_start_matches('0');
if trimmed.is_empty() {
"0".to_string()
} else {
trimmed.to_string()
}
}
fn common_name(cert: &Certificate) -> Option<String> {
for rdn in cert.tbs_certificate.subject.0.iter() {
for atav in rdn.0.iter() {
if atav.oid == OID_AT_COMMON_NAME {
let raw = atav.value.value();
return Some(String::from_utf8_lossy(raw).into_owned());
}
}
}
None
}
fn valid_at(cert: &Certificate, t: OffsetDateTime) -> bool {
let nb = cert
.tbs_certificate
.validity
.not_before
.to_unix_duration()
.as_secs() as i64;
let na = cert
.tbs_certificate
.validity
.not_after
.to_unix_duration()
.as_secs() as i64;
let now = t.unix_timestamp();
nb <= now && now <= na
}
fn is_ca_certificate(cert: &Certificate) -> bool {
let Some(exts) = cert.tbs_certificate.extensions.as_ref() else {
return false;
};
let Some(ext) = exts.iter().find(|e| e.extn_id == OID_EXT_BASIC_CONSTRAINTS) else {
return false;
};
BasicConstraints::from_der(ext.extn_value.as_bytes())
.map(|bc| bc.ca)
.unwrap_or(false)
}
fn path_len_constraint(cert: &Certificate) -> Option<usize> {
let exts = cert.tbs_certificate.extensions.as_ref()?;
let ext = exts
.iter()
.find(|e| e.extn_id == OID_EXT_BASIC_CONSTRAINTS)?;
let bc = BasicConstraints::from_der(ext.extn_value.as_bytes()).ok()?;
bc.path_len_constraint.map(|n| n as usize)
}
fn has_key_cert_sign(cert: &Certificate) -> bool {
let Some(exts) = cert.tbs_certificate.extensions.as_ref() else {
return false;
};
let Some(ext) = exts.iter().find(|e| e.extn_id == OID_EXT_KEY_USAGE) else {
return false;
};
use x509_cert::ext::pkix::KeyUsage;
KeyUsage::from_der(ext.extn_value.as_bytes())
.map(|ku| ku.key_cert_sign())
.unwrap_or(false)
}
fn allows_digital_signature(cert: &Certificate) -> bool {
let Some(exts) = cert.tbs_certificate.extensions.as_ref() else {
return false;
};
let Some(ext) = exts.iter().find(|e| e.extn_id == OID_EXT_KEY_USAGE) else {
return false;
};
use x509_cert::ext::pkix::KeyUsage;
KeyUsage::from_der(ext.extn_value.as_bytes())
.map(|ku| ku.digital_signature())
.unwrap_or(false)
}
pub fn leaf_profile_acceptable_der(leaf_der: &[u8]) -> bool {
Certificate::from_der(leaf_der)
.is_ok_and(|leaf| profile::profile_violation(&leaf, profile::CertRole::EndEntity).is_none())
}
fn leaf_is_acceptable_claim_signer(leaf: &Certificate) -> bool {
if profile::profile_violation(leaf, profile::CertRole::EndEntity).is_some() {
return false;
}
let Some(ekus) = certificate_eku_oids(leaf) else {
return false;
};
if ekus
.iter()
.any(|oid| oid == OID_KP_TIME_STAMPING || oid == OID_KP_OCSP_SIGNING)
{
return false;
}
let policy = EkuPolicy::default();
ekus.iter()
.any(|oid| policy.allowed_oids.iter().any(|allowed| allowed == oid))
}
pub fn leaf_acceptable_der(leaf_der: &[u8]) -> bool {
Certificate::from_der(leaf_der)
.map(|c| leaf_is_acceptable_claim_signer(&c))
.unwrap_or(false)
}
fn verify_signature(subject: &Certificate, issuer: &Certificate) -> bool {
let Ok(tbs) = subject.tbs_certificate.to_der() else {
return false;
};
let Some(sig) = subject.signature.as_bytes() else {
return false;
};
let spki = &issuer.tbs_certificate.subject_public_key_info;
let Some(pubkey) = spki.subject_public_key.as_bytes() else {
return false;
};
let sig_alg = subject.signature_algorithm.oid;
let key_alg = spki.algorithm.oid;
if key_alg == OID_EC_PUBLIC_KEY {
let hash = match sig_alg {
OID_ECDSA_SHA256 => SigHash::Sha256,
OID_ECDSA_SHA384 => SigHash::Sha384,
OID_ECDSA_SHA512 => SigHash::Sha512,
_ => return false,
};
let curve = match spki.algorithm.parameters.as_ref() {
Some(p) => match p.decode_as::<ObjectIdentifier>() {
Ok(oid) => oid,
Err(_) => return false,
},
None => return false,
};
verify_ecdsa(curve, hash, pubkey, sig, &tbs)
} else if key_alg == OID_RSA_ENCRYPTION {
let hash = match sig_alg {
OID_RSA_SHA256 => SigHash::Sha256,
OID_RSA_SHA384 => SigHash::Sha384,
OID_RSA_SHA512 => SigHash::Sha512,
_ => return false,
};
verify_rsa(hash, pubkey, sig, &tbs)
} else if key_alg == OID_ED25519 {
verify_ed25519(pubkey, sig, &tbs)
} else {
false
}
}
fn verify_ecdsa(
curve: ObjectIdentifier,
hash: SigHash,
pubkey: &[u8],
sig_der: &[u8],
tbs: &[u8],
) -> bool {
let prehash = digest_bytes(hash, tbs);
if curve == OID_CURVE_P256 {
let (Ok(vk), Ok(sig)) = (
p256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey),
p256::ecdsa::Signature::from_der(sig_der),
) else {
return false;
};
vk.verify_prehash(&prehash, &sig).is_ok()
} else if curve == OID_CURVE_P384 {
let (Ok(vk), Ok(sig)) = (
p384::ecdsa::VerifyingKey::from_sec1_bytes(pubkey),
p384::ecdsa::Signature::from_der(sig_der),
) else {
return false;
};
vk.verify_prehash(&prehash, &sig).is_ok()
} else if curve == OID_CURVE_P521 {
let (Ok(vk), Ok(sig)) = (
p521::ecdsa::VerifyingKey::from_sec1_bytes(pubkey),
p521::ecdsa::Signature::from_der(sig_der),
) else {
return false;
};
vk.verify_prehash(&prehash, &sig).is_ok()
} else {
false
}
}
fn verify_rsa(hash: SigHash, pubkey_der: &[u8], sig: &[u8], tbs: &[u8]) -> bool {
let Ok(pubkey) = rsa::RsaPublicKey::from_pkcs1_der(pubkey_der) else {
return false;
};
let Ok(signature) = rsa::pkcs1v15::Signature::try_from(sig) else {
return false;
};
match hash {
SigHash::Sha256 => rsa::pkcs1v15::VerifyingKey::<Sha256>::new(pubkey)
.verify(tbs, &signature)
.is_ok(),
SigHash::Sha384 => rsa::pkcs1v15::VerifyingKey::<Sha384>::new(pubkey)
.verify(tbs, &signature)
.is_ok(),
SigHash::Sha512 => rsa::pkcs1v15::VerifyingKey::<Sha512>::new(pubkey)
.verify(tbs, &signature)
.is_ok(),
}
}
fn verify_ed25519(pubkey: &[u8], sig: &[u8], tbs: &[u8]) -> bool {
let Ok(key_bytes): Result<[u8; 32], _> = pubkey.try_into() else {
return false;
};
let Ok(vk) = ed25519_dalek::VerifyingKey::from_bytes(&key_bytes) else {
return false;
};
let Ok(signature) = ed25519_dalek::Signature::from_slice(sig) else {
return false;
};
vk.verify_strict(tbs, &signature).is_ok()
}
#[cfg(test)]
mod tests;