pdf-signature-reader-sl
Version:
Verify digital signatures in PDF documents with content validation and enhanced support for Spanish certificates (FNMT, DNIe, corporate)
123 lines (100 loc) • 3.28 kB
JavaScript
const forge = require('node-forge');
const getCertificateName = (cert) => {
const cn = cert.subject.getField('CN');
if (cn) return cn.value;
const ou = cert.subject.getField('OU');
if (ou) return ou.value;
const o = cert.subject.getField('O');
if (o) return o.value;
return 'Unknown';
};
const getIssuerName = (cert) => {
const cn = cert.issuer.getField('CN');
if (cn) return cn.value;
const ou = cert.issuer.getField('OU');
if (ou) return ou.value;
const o = cert.issuer.getField('O');
if (o) return o.value;
return 'Unknown';
};
const findSigningCertificate = (leafCerts, signature, digest) => {
if (leafCerts.length === 1) return leafCerts[0];
for (const cert of leafCerts) {
try {
if (cert.publicKey.verify(digest, signature)) {
return cert;
}
} catch (error) {
continue;
}
}
return null;
};
const buildRobustCertificateChain = (certificates, signature, digest) => {
const certsBySubjectHash = new Map();
const certsByIssuerHash = new Map();
const uniqueCerts = [];
const seenKeys = new Set();
for (const cert of certificates) {
const key = `${cert.subject.hash}-${cert.issuer.hash}`;
if (seenKeys.has(key)) continue;
seenKeys.add(key);
uniqueCerts.push(cert);
if (!certsBySubjectHash.has(cert.subject.hash)) {
certsBySubjectHash.set(cert.subject.hash, []);
}
certsBySubjectHash.get(cert.subject.hash).push(cert);
if (!certsByIssuerHash.has(cert.issuer.hash)) {
certsByIssuerHash.set(cert.issuer.hash, []);
}
certsByIssuerHash.get(cert.issuer.hash).push(cert);
}
const leafCerts = uniqueCerts.filter(cert =>
!certsByIssuerHash.has(cert.subject.hash)
);
if (!leafCerts.length) {
return {
chain: [],
isComplete: false,
error: 'No leaf certificate found'
};
}
let actualSigningCert = leafCerts[0];
if (leafCerts.length > 1 && signature && digest) {
const signingCert = findSigningCertificate(leafCerts, signature, digest);
if (signingCert) {
actualSigningCert = signingCert;
}
}
const chain = [];
let currentCert = actualSigningCert;
const visitedHashes = new Set();
while (currentCert) {
if (visitedHashes.has(currentCert.subject.hash)) break;
visitedHashes.add(currentCert.subject.hash);
const isRoot = currentCert.subject.hash === currentCert.issuer.hash;
chain.push({
cert: currentCert.serialNumber,
certificate: currentCert,
level: chain.length,
isRoot: isRoot,
subjectCN: getCertificateName(currentCert),
issuerCN: getIssuerName(currentCert),
isSigningCert: currentCert === actualSigningCert
});
if (isRoot) break;
const issuers = certsBySubjectHash.get(currentCert.issuer.hash);
currentCert = issuers?.[0] || null;
if (!currentCert) break;
}
const isComplete = chain.length > 0 && chain[chain.length - 1].isRoot;
return {
chain,
isComplete,
signingCertificate: actualSigningCert.serialNumber,
rootCertificate: isComplete ? chain[chain.length - 1].cert : null,
};
};
module.exports = {
buildRobustCertificateChain,
};