#! /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": "/certs.pem",'); println(' "privKey": "/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 [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();