use sha2::{Digest, Sha256};
use zpdf_core::{ObjectId, PdfDict, PdfName, PdfObject, Result};
use crate::metadata::encode_text_string;
use crate::serialize::serialize_object_body;
use crate::{invalid_data, IncrementalWriter};
const RESERVED_HEX: usize = 16_384;
pub enum SigningKey {
EcdsaP256(Box<p256::ecdsa::SigningKey>),
Rsa(Box<rsa::RsaPrivateKey>),
}
impl SigningKey {
pub fn ecdsa_p256_from_scalar(scalar: &[u8]) -> Result<Self> {
let key = p256::ecdsa::SigningKey::from_slice(scalar)
.map_err(|_| invalid_data("invalid P-256 private scalar"))?;
Ok(SigningKey::EcdsaP256(Box::new(key)))
}
pub fn rsa_from_pkcs1_der(der: &[u8]) -> Result<Self> {
use rsa::pkcs1::DecodeRsaPrivateKey;
let key = rsa::RsaPrivateKey::from_pkcs1_der(der)
.map_err(|_| invalid_data("invalid PKCS#1 RSA private key"))?;
Ok(SigningKey::Rsa(Box::new(key)))
}
pub fn from_pkcs8_der(der: &[u8]) -> Result<Self> {
use p256::pkcs8::DecodePrivateKey as _;
if let Ok(key) = rsa::RsaPrivateKey::from_pkcs8_der(der) {
return Ok(SigningKey::Rsa(Box::new(key)));
}
if let Ok(key) = p256::ecdsa::SigningKey::from_pkcs8_der(der) {
return Ok(SigningKey::EcdsaP256(Box::new(key)));
}
Err(invalid_data("PKCS#8 key is neither RSA nor P-256").into())
}
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
match self {
SigningKey::EcdsaP256(key) => {
use p256::ecdsa::signature::hazmat::PrehashSigner;
let hash = Sha256::digest(msg);
let sig: p256::ecdsa::Signature = key
.sign_prehash(&hash)
.map_err(|_| invalid_data("ECDSA signing failed"))?;
Ok(sig.to_der().as_bytes().to_vec())
}
SigningKey::Rsa(key) => {
use rsa::pkcs1v15::SigningKey as RsaSigningKey;
use rsa::signature::{SignatureEncoding, Signer};
let signing_key = RsaSigningKey::<Sha256>::new((**key).clone());
Ok(signing_key.sign(msg).to_vec())
}
}
}
fn sig_alg_oid(&self) -> &'static [u8] {
match self {
SigningKey::EcdsaP256(_) => &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02],
SigningKey::Rsa(_) => &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b],
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SignatureOptions {
pub name: Option<String>,
pub reason: Option<String>,
pub location: Option<String>,
pub contact: Option<String>,
pub field_name: Option<String>,
}
impl IncrementalWriter {
pub fn sign(
mut self,
certificate_der: &[u8],
key: &SigningKey,
options: &SignatureOptions,
) -> Result<Vec<u8>> {
if certificate_der.is_empty() {
return Err(invalid_data("certificate must not be empty").into());
}
let field_name = options.field_name.as_deref().unwrap_or("Signature1");
let mut sig_body = Vec::new();
sig_body.extend_from_slice(
b"<< /Type /Sig /Filter /Adobe.PPKLite /SubFilter /adbe.pkcs7.detached",
);
for (k, v) in [
("Name", &options.name),
("Reason", &options.reason),
("Location", &options.location),
("ContactInfo", &options.contact),
] {
if let Some(text) = v {
sig_body.extend_from_slice(format!(" /{k} ").as_bytes());
serialize_object_body(&mut sig_body, &PdfObject::String(encode_text_string(text)))?;
}
}
sig_body.extend_from_slice(format!(" /M (D:{}Z)", pdf_timestamp()).as_bytes());
sig_body.extend_from_slice(b" /ByteRange [0000000000 0000000000 0000000000 0000000000]");
sig_body.extend_from_slice(b" /Contents <");
sig_body.extend_from_slice(&vec![b'0'; RESERVED_HEX]);
sig_body.extend_from_slice(b"> >>");
self.ensure_object_capacity(2)?;
let (sig_num, _) = self.try_add_raw_object(&sig_body)?;
let sig_ref = ObjectId(sig_num, 0);
let page_id = self.page_id(0)?;
let mut field = PdfDict::new();
field.insert(PdfName::new("FT"), PdfObject::Name(PdfName::new("Sig")));
field.insert(
PdfName::new("T"),
PdfObject::String(encode_text_string(field_name)),
);
field.insert(PdfName::new("V"), PdfObject::Ref(sig_ref));
field.insert(PdfName::new("Type"), PdfObject::Name(PdfName::new("Annot")));
field.insert(
PdfName::new("Subtype"),
PdfObject::Name(PdfName::new("Widget")),
);
field.insert(
PdfName::new("Rect"),
PdfObject::Array(vec![
PdfObject::Integer(0),
PdfObject::Integer(0),
PdfObject::Integer(0),
PdfObject::Integer(0),
]),
);
field.insert(PdfName::new("F"), PdfObject::Integer(4));
field.insert(PdfName::new("P"), PdfObject::Ref(page_id));
let (field_num, _) = self.try_add_object(&PdfObject::Dict(field))?;
let field_ref = ObjectId(field_num, 0);
let page_obj = self.resolve_current(page_id)?;
let mut page_dict = page_obj.as_dict()?.clone();
let mut annots = match page_dict.get("Annots") {
Some(PdfObject::Ref(r)) => match self.resolve_current(*r) {
Ok(obj) => obj.as_array().ok().map(|a| a.to_vec()).unwrap_or_default(),
Err(_) => Vec::new(),
},
Some(PdfObject::Array(arr)) => arr.to_vec(),
_ => Vec::new(),
};
annots.push(PdfObject::Ref(field_ref));
page_dict.insert(PdfName::new("Annots"), PdfObject::Array(annots));
self.overwrite_object(page_id, PdfObject::Dict(page_dict));
let catalog_id = self.catalog_ref();
let catalog = self.resolve_current(catalog_id)?;
let mut catalog_dict = catalog.as_dict()?.clone();
let (acro_id, mut acro_dict) = match catalog_dict.get("AcroForm") {
Some(PdfObject::Ref(r)) => {
let d = self.resolve_current(*r)?.as_dict()?.clone();
(Some(*r), d)
}
Some(PdfObject::Dict(d)) => (None, d.clone()),
_ => (None, PdfDict::new()),
};
let mut fields = match acro_dict.get("Fields") {
Some(PdfObject::Array(a)) => a.clone(),
Some(PdfObject::Ref(r)) => match self.resolve_current(*r) {
Ok(PdfObject::Array(a)) => a,
_ => Vec::new(),
},
_ => Vec::new(),
};
fields.push(PdfObject::Ref(field_ref));
acro_dict.insert(PdfName::new("Fields"), PdfObject::Array(fields));
acro_dict.insert(PdfName::new("SigFlags"), PdfObject::Integer(3));
match acro_id {
Some(id) => self.overwrite_object(id, PdfObject::Dict(acro_dict)),
None => {
catalog_dict.insert(PdfName::new("AcroForm"), PdfObject::Dict(acro_dict));
self.overwrite_object(catalog_id, PdfObject::Dict(catalog_dict));
}
}
let mut cursor = std::io::Cursor::new(Vec::new());
self.write(&mut cursor).map_err(zpdf_core::Error::Io)?;
let mut buf = cursor.into_inner();
let tail_start = self.document().file().data().len();
let br_marker = b"/ByteRange [0000000000";
let br_at = find_from(&buf, br_marker, tail_start)
.ok_or_else(|| invalid_data("ByteRange placeholder not found"))?;
let contents_marker = b"/Contents <";
let contents_at = find_from(&buf, contents_marker, br_at)
.ok_or_else(|| invalid_data("Contents placeholder not found"))?;
let contents_start = contents_at + contents_marker.len() - 1; let contents_end = contents_start + 1 + RESERVED_HEX + 1; if buf.get(contents_end - 1) != Some(&b'>') {
return Err(invalid_data("Contents window corrupt").into());
}
let ranges = [
0usize,
contents_start,
contents_end,
buf.len() - contents_end,
];
let br_open = br_at + b"/ByteRange ".len(); for (i, &v) in ranges.iter().enumerate() {
if v > 9_999_999_999 {
return Err(invalid_data("file too large for ByteRange slots").into());
}
let slot = br_open + 1 + i * 11;
buf[slot..slot + 10].copy_from_slice(format!("{v:010}").as_bytes());
}
let mut hasher = Sha256::new();
hasher.update(&buf[..contents_start]);
hasher.update(&buf[contents_end..]);
let digest = hasher.finalize();
let cms = build_cms(&digest, certificate_der, key)?;
let hex = to_hex(&cms);
if hex.len() > RESERVED_HEX {
return Err(invalid_data("CMS exceeds the reserved /Contents window").into());
}
buf[contents_start + 1..contents_start + 1 + hex.len()].copy_from_slice(&hex);
Ok(buf)
}
}
const SEQ: u8 = 0x30;
const SET: u8 = 0x31;
const OID: u8 = 0x06;
const OCTET: u8 = 0x04;
const INT: u8 = 0x02;
const CTX0: u8 = 0xA0;
const OID_SHA256: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01];
const OID_CONTENT_TYPE: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03];
const OID_MESSAGE_DIGEST: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04];
const OID_DATA: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01];
const OID_SIGNED_DATA: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02];
fn der(tag: u8, content: &[u8]) -> Vec<u8> {
let mut out = vec![tag];
let len = content.len();
if len < 0x80 {
out.push(len as u8);
} else if len < 0x100 {
out.extend_from_slice(&[0x81, len as u8]);
} else if len < 0x10000 {
out.extend_from_slice(&[0x82, (len >> 8) as u8, (len & 0xff) as u8]);
} else {
out.extend_from_slice(&[
0x83,
(len >> 16) as u8,
((len >> 8) & 0xff) as u8,
(len & 0xff) as u8,
]);
}
out.extend_from_slice(content);
out
}
fn build_cms(digest: &[u8], cert: &[u8], key: &SigningKey) -> Result<Vec<u8>> {
let digest_alg = der(SEQ, &der(OID, OID_SHA256));
let ct_attr = der(
SEQ,
&[der(OID, OID_CONTENT_TYPE), der(SET, &der(OID, OID_DATA))].concat(),
);
let md_attr = der(
SEQ,
&[der(OID, OID_MESSAGE_DIGEST), der(SET, &der(OCTET, digest))].concat(),
);
let attrs = [ct_attr, md_attr].concat();
let signature = key.sign(&der(SET, &attrs))?;
let signer_info = der(
SEQ,
&[
der(INT, &[1]),
der(SEQ, &[]), digest_alg.clone(),
der(CTX0, &attrs), der(SEQ, &der(OID, key.sig_alg_oid())),
der(OCTET, &signature),
]
.concat(),
);
let signed_data = der(
SEQ,
&[
der(INT, &[1]),
der(SET, &digest_alg),
der(SEQ, &der(OID, OID_DATA)),
der(CTX0, cert), der(SET, &signer_info),
]
.concat(),
);
Ok(der(
SEQ,
&[der(OID, OID_SIGNED_DATA), der(CTX0, &signed_data)].concat(),
))
}
fn to_hex(bytes: &[u8]) -> Vec<u8> {
const HEX: &[u8] = b"0123456789abcdef";
let mut out = Vec::with_capacity(bytes.len() * 2);
for &b in bytes {
out.push(HEX[(b >> 4) as usize]);
out.push(HEX[(b & 0xf) as usize]);
}
out
}
fn find_from(haystack: &[u8], needle: &[u8], from: usize) -> Option<usize> {
let start = from.min(haystack.len());
haystack[start..]
.windows(needle.len())
.position(|w| w == needle)
.map(|p| p + start)
}
fn pdf_timestamp() -> String {
crate::metadata::pdf_date_now_raw()
}