use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl::nid::Nid;
use openssl::pkcs7::Pkcs7;
use openssl::stack::StackRef;
use openssl::x509::{X509, X509NameRef, X509Ref};
use openssl_sys::{
ASN1_OBJECT, OBJ_obj2nid, OBJ_obj2txt, OPENSSL_sk_free, OPENSSL_sk_num, OPENSSL_sk_value,
PKCS7_SIGNER_INFO, PKCS7_get_signer_info, PKCS7_get0_signers, X509_ALGOR_get0,
};
use crate::digest::Digest;
use crate::error::{Error, Result};
const OID_RFC3161: &str = "1.3.6.1.4.1.311.3.3.1";
const OID_COUNTERSIGNATURE: &str = "1.2.840.113549.1.9.6";
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct SignatureInfo {
pub signers: Vec<Signer>,
pub digest: Option<Digest>,
pub timestamped: bool,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct Signer {
pub subject: String,
pub issuer: String,
pub serial: String,
pub not_before: String,
pub not_after: String,
}
pub(crate) fn from_der(der: &[u8]) -> Result<SignatureInfo> {
let pkcs7 = Pkcs7::from_der(der).map_err(|error| Error::Runtime {
message: format!("failed to parse PKCS#7 signature: {error}"),
})?;
let raw = pkcs7.as_ptr();
Ok(SignatureInfo {
signers: collect_signers(raw),
digest: first_signer_digest(raw),
timestamped: is_timestamped(raw),
})
}
fn collect_signers(pkcs7: *mut openssl_sys::PKCS7) -> Vec<Signer> {
let stack = unsafe { PKCS7_get0_signers(pkcs7, ptr::null_mut(), 0) };
if stack.is_null() {
return Vec::new();
}
let signers = unsafe { StackRef::<X509>::from_ptr(stack) }
.iter()
.map(signer_from_cert)
.collect();
unsafe { OPENSSL_sk_free(stack.cast()) };
signers
}
fn signer_from_cert(cert: &X509Ref) -> Signer {
Signer {
subject: name_string(cert.subject_name()),
issuer: name_string(cert.issuer_name()),
serial: cert
.serial_number()
.to_bn()
.and_then(|bn| bn.to_hex_str())
.map(|hex| hex.to_string())
.unwrap_or_default(),
not_before: cert.not_before().to_string(),
not_after: cert.not_after().to_string(),
}
}
fn name_string(name: &X509NameRef) -> String {
name.entries()
.map(|entry| {
let key = entry.object().nid().short_name().unwrap_or("?");
let value = String::from_utf8_lossy(entry.data().as_slice());
format!("{key}={value}")
})
.collect::<Vec<_>>()
.join(", ")
}
fn first_signer_info(pkcs7: *mut openssl_sys::PKCS7) -> *mut PKCS7_SIGNER_INFO {
let infos = unsafe { PKCS7_get_signer_info(pkcs7) };
if infos.is_null() || unsafe { OPENSSL_sk_num(infos.cast()) } < 1 {
return ptr::null_mut();
}
unsafe { OPENSSL_sk_value(infos.cast(), 0) }.cast()
}
fn first_signer_digest(pkcs7: *mut openssl_sys::PKCS7) -> Option<Digest> {
let info = first_signer_info(pkcs7);
if info.is_null() {
return None;
}
let algorithm = unsafe { (*info).digest_alg };
if algorithm.is_null() {
return None;
}
let mut object: *const ASN1_OBJECT = ptr::null();
unsafe { X509_ALGOR_get0(&mut object, ptr::null_mut(), ptr::null_mut(), algorithm) };
if object.is_null() {
return None;
}
Digest::try_from(Nid::from_raw(unsafe { OBJ_obj2nid(object) })).ok()
}
impl TryFrom<Nid> for Digest {
type Error = ();
fn try_from(nid: Nid) -> std::result::Result<Self, Self::Error> {
match nid {
Nid::MD5 => Ok(Self::Md5),
Nid::SHA1 => Ok(Self::Sha1),
Nid::SHA256 => Ok(Self::Sha256),
Nid::SHA384 => Ok(Self::Sha384),
Nid::SHA512 => Ok(Self::Sha512),
_ => Err(()),
}
}
}
fn is_timestamped(pkcs7: *mut openssl_sys::PKCS7) -> bool {
let info = first_signer_info(pkcs7);
if info.is_null() {
return false;
}
let attributes = unsafe { (*info).unauth_attr };
if attributes.is_null() {
return false;
}
let count = unsafe { OPENSSL_sk_num(attributes.cast()) };
(0..count).any(|index| {
let attribute = unsafe { OPENSSL_sk_value(attributes.cast(), index) };
let object = unsafe { openssl_sys::X509_ATTRIBUTE_get0_object(attribute.cast()) };
matches!(
oid_text(object).as_deref(),
Some(OID_RFC3161 | OID_COUNTERSIGNATURE)
)
})
}
fn oid_text(object: *const ASN1_OBJECT) -> Option<String> {
if object.is_null() {
return None;
}
let mut buffer = [0 as c_char; 128];
let written = unsafe { OBJ_obj2txt(buffer.as_mut_ptr(), buffer.len() as c_int, object, 1) };
if written <= 0 {
return None;
}
unsafe { CStr::from_ptr(buffer.as_ptr()) }
.to_str()
.ok()
.map(str::to_owned)
}