141 lines
5.9 KiB
JavaScript
141 lines
5.9 KiB
JavaScript
#! /usr/bin/env runjs
|
|
|
|
requireJAR('bcpkix-jdk15on-154.jar');
|
|
requireJAR('bcprov-jdk15on-154.jar');
|
|
requireJAR('bcprov-ext-jdk15on-154.jar');
|
|
requireJAR('itextpdf-5.5.11.jar');
|
|
|
|
var JavaArray = java.lang.reflect.Array;
|
|
var Security = java.security.Security;
|
|
var StringReader = java.io.StringReader;
|
|
var BufferedReader = java.io.BufferedReader;
|
|
var FileOutputStream = java.io.FileOutputStream;
|
|
var X509Certificate = java.security.cert.X509Certificate;
|
|
var Rectangle = Packages.com.itextpdf.text.Rectangle;
|
|
var PdfName = Packages.com.itextpdf.text.pdf.PdfName;
|
|
var PdfReader = Packages.com.itextpdf.text.pdf.PdfReader;
|
|
var PdfStamper = Packages.com.itextpdf.text.pdf.PdfStamper;
|
|
var PdfPKCS7 = Packages.com.itextpdf.text.pdf.security.PdfPKCS7;
|
|
var MakeSignature = Packages.com.itextpdf.text.pdf.security.MakeSignature;
|
|
var DigestAlgorithms = Packages.com.itextpdf.text.pdf.security.DigestAlgorithms;
|
|
var BouncyCastleDigest = Packages.com.itextpdf.text.pdf.security.BouncyCastleDigest;
|
|
var PrivateKeySignature = Packages.com.itextpdf.text.pdf.security.PrivateKeySignature;
|
|
var CryptoStandard = Packages.com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
|
|
var ExternalSignatureContainer = Packages.com.itextpdf.text.pdf.security.ExternalSignatureContainer;
|
|
var ExternalBlankSignatureContainer = Packages.com.itextpdf.text.pdf.security.ExternalBlankSignatureContainer;
|
|
var BouncyCastleProvider = Packages.org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
var PrivateKeyParseTool = Packages.me.hatter.tools.commons.security.rsa.PrivateKeyParseTool;
|
|
var X509CertUtil = Packages.me.hatter.tools.commons.security.cert.X509CertUtil;
|
|
|
|
var emptySignature = (src, dest, fieldname, chain, reason, location) => {
|
|
var reader = new PdfReader(src);
|
|
var os = new FileOutputStream(dest);
|
|
var stamper = PdfStamper.createSignature(reader, os, '\0');
|
|
var appearance = stamper.getSignatureAppearance();
|
|
appearance.setVisibleSignature(new Rectangle(20, 748, 180, 780), 1, fieldname);
|
|
appearance.setCertificate(chain[0]);
|
|
if (reason) {
|
|
appearance.setReason(reason);
|
|
}
|
|
if (location) {
|
|
appearance.setLocation(location);
|
|
}
|
|
var external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
|
|
MakeSignature.signExternalContainer(appearance, external, 8192);
|
|
};
|
|
|
|
var createSignature = (src, dest, fieldname, pk, chain) => {
|
|
var reader = new PdfReader(src);
|
|
var os = new FileOutputStream(dest);
|
|
var external = new ExternalSignatureContainer({
|
|
'sign': (is) => {
|
|
var signature = new PrivateKeySignature(pk, "SHA256", "BC");
|
|
var hashAlgorithm = signature.getHashAlgorithm();
|
|
var digest = new BouncyCastleDigest();
|
|
var sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
|
|
var hash = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
|
|
var sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
|
|
var extSignature = signature.sign(sh);
|
|
sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
|
|
return sgn.getEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
|
|
},
|
|
'modifySigningDictionary': (signDic) => {
|
|
// DO NOTHING
|
|
}
|
|
});
|
|
MakeSignature.signDeferred(reader, fieldname, os, external);
|
|
};
|
|
|
|
var printConfigFileFormat = () => {
|
|
println('Format:');
|
|
println('{');
|
|
println(' "certs": "<PATH>/certs.pem",');
|
|
println(' "privKey": "<PATH>/privKey.pem.gpg"');
|
|
println('}');
|
|
};
|
|
|
|
var main = () => {
|
|
if ($ARGS == null || $ARGS.length == 0) {
|
|
println('signpdf.js - Sign PDF.')
|
|
println();
|
|
println('ERROR: NO arguments assigned!');
|
|
println('signpdf.js <file.pdf> [reason [location]]');
|
|
return;
|
|
}
|
|
var reason = ($ARGS.length > 1)? $ARGS[1]: null;
|
|
var location = ($ARGS.length > 2)? $ARGS[2]: null;
|
|
var pdf = $$.file($ARGS[0]);
|
|
if (!pdf.exists()) {
|
|
println('File not exists: ' + pdf);
|
|
return;
|
|
}
|
|
if (!pdf.getName().toLowerCase().endsWith('.pdf')) {
|
|
println('File is not PDF: ' + pdf);
|
|
return;
|
|
}
|
|
var configFile = $$.file($$.prop('user.home'), '.signpdf.js.json');
|
|
if (!configFile.exists()) {
|
|
println('SignPDF.js config file not found: ' + configFile);
|
|
printConfigFileFormat();
|
|
return;
|
|
}
|
|
var configObject = JSON.parse($$.rFile(configFile).rReader().stringAndClose());
|
|
if (!configObject.privKey || !configObject.certs) {
|
|
println('Config file error: ' + configFile);
|
|
printConfigFileFormat();
|
|
return;
|
|
}
|
|
if ($$.file($ARGS[0].replace('.pdf', '-signed.pdf')).exists()) {
|
|
println('Target file exists: ' + $ARGS[0].replace('.pdf', '-signed.pdf'));
|
|
return;
|
|
}
|
|
Security.addProvider(new BouncyCastleProvider());
|
|
|
|
var result = $$.shell().commands('sh', '-c', 'cat ' + configObject.privKey + ' | gpg').start();
|
|
var out = result[0].rStream().rReader().stringAndClose();
|
|
var err = result[1].rStream().rReader().stringAndClose();
|
|
if (err.contains('public key decryption failed')) {
|
|
println('+ Decrypt file FAILED: ' + configObject.privKey);
|
|
if (!Packages.me.hatter.tools.jssp.main.StandaloneMain.JSSP_MAIN_MUTE) {
|
|
println("ERROR detail:\n" + err);
|
|
}
|
|
return;
|
|
}
|
|
|
|
var privateKeyParseTool = new PrivateKeyParseTool(new BufferedReader(new StringReader(out)));
|
|
var certList = X509CertUtil.parseX509CertificateList($$.rFile(configObject.certs).rStream().bytesAndClose());
|
|
|
|
var chain = certList.toArray(JavaArray.newInstance(certList.get(0).getClass(), certList.size()));
|
|
var pk = privateKeyParseTool.read();
|
|
var temp = '~' + $$.date().millis() + '.pdf';
|
|
try {
|
|
emptySignature(pdf, temp, "sig", chain, reason, location);
|
|
createSignature(temp, $ARGS[0].replace('.pdf', '-signed.pdf'), "sig", pk, chain);
|
|
} finally {
|
|
$$.file(temp).delete();
|
|
}
|
|
};
|
|
|
|
main();
|
|
|