sorry for double post, indentation wasn't preserved so here's a pastebin : https://pastebin.com/b3qZH6xW
On Wed, May 24, 2017 at 3:42 PM Paresh Chouhan <[email protected]> wrote: > I have a service which signs the data and provides me with the signed > hash, it correctly generates PKCS#7 DigestInfo as stated here > https://tools.ietf.org/html/rfc2315#section-9.4 > > find the code at the end of this email, > the problem is the generated PDF is shown as invalid > > public class DetachedPkcs7 implements SignatureInterface { > public static void main(String[] args) { > Security.addProvider(new > org.bouncycastle.jce.provider.BouncyCastleProvider()); > try { > //load pdf document > PDDocument document = PDDocument.load(new File("test.pdf")); > int accessPermissions = getMDPPermission(document); > if (accessPermissions == 1) > { > throw new IllegalStateException("No changes to the document > are permitted due to DocMDP transform parameters dictionary"); > } > //prepare signature > PDSignature signature = new PDSignature(); > signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); > signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); > signature.setName("Ankit Agarwal"); > signature.setLocation("Bhopal, IN"); > signature.setReason("Testing"); > // TODO extract the above details from the signing certificate? > Reason as a parameter? > > // the signing date, needed for valid signature > signature.setSignDate(Calendar.getInstance()); > > // // Optional: certify > if (accessPermissions == 0) > { > setMDPPermission(document, signature, 3); > } > > FileOutputStream fos = new FileOutputStream(new > File("signed_file.pdf")); > > DetachedPkcs7 detachedPkcs7 = new DetachedPkcs7(); > //populate signature options for visible signature. if any. > SignatureOptions signatureOptions = null; > document.addSignature(signature); > ExternalSigningSupport externalSigning = > document.saveIncrementalForExternalSigning(fos); > InputStream dataToSign = externalSigning.getContent(); > byte[] cmsSignature = detachedPkcs7.sign(dataToSign); > externalSigning.setSignature(cmsSignature); > } > catch(FileNotFoundException fex) { > fex.printStackTrace(); > } catch (IOException e) { > e.printStackTrace(); > } > } > > class CMSTypedDataInputStream implements CMSTypedData { > InputStream in; > > public CMSTypedDataInputStream(InputStream is) { > in = is; > } > > @Override > public ASN1ObjectIdentifier getContentType() { > return PKCSObjectIdentifiers.data; > } > > @Override > public Object getContent() { > return in; > } > > @Override > public void write(OutputStream out) throws IOException, > CMSException { > byte[] buffer = new byte[8 * 1024]; > int read; > while ((read = in.read(buffer)) != -1) { > out.write(buffer, 0, read); > } > in.close(); > } > } > > static class CMSProcessableInputStream implements CMSProcessable, > CMSTypedData { > private InputStream input; > private boolean used = false; > private final ASN1ObjectIdentifier contentType; > > public CMSProcessableInputStream( > InputStream input) > { > this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), > input); > } > > public CMSProcessableInputStream(ASN1ObjectIdentifier contentType, > InputStream input) { > this.input = input; > this.contentType = contentType; > } > > public InputStream getInputStream() > { > // checkSingleUsage(); > > return input; > } > > public void write(OutputStream zOut) > throws IOException, CMSException > { > // checkSingleUsage(); > > Streams.pipeAll(input, zOut); > input.close(); > } > > public Object getContent() > { > return getInputStream(); > } > > // private synchronized void checkSingleUsage() > // { > // if (used) > // { > // throw new IllegalStateException("CMSProcessableInputStream > can only be used once"); > // } > // > // used = true; > // } > > @Override > public ASN1ObjectIdentifier getContentType() { > return contentType; > } > } > > public byte[] sign(InputStream content) throws IOException { > try { > //prepare message digest > MessageDigest mdOriginal = MessageDigest.getInstance("SHA-256"); > byte[] dataToDigest = IOUtils.toByteArray(content); > byte[] digestOriginal = mdOriginal.digest(dataToDigest); > System.out.println("SHA256Original : " + > Hex.encodeHexString(digestOriginal)); > //the certificate of the signer. > String certPem = "-----BEGIN CERTIFICATE-----\n" + > "USER CERT HERE, THIS IS ALSO TAKEN FROM USB TOKEN > (HSM)\n" + > "-----END CERTIFICATE-----"; > ByteArrayInputStream inStream = new > ByteArrayInputStream(certPem.getBytes()); > > BufferedInputStream bis = new BufferedInputStream(inStream); > System.out.println(bis.available()); > CertificateFactory cf = null; > > cf = CertificateFactory.getInstance("X.509"); > List<Certificate> certList = new ArrayList<Certificate>(); > //generate certificate from input stream > Certificate certificate = cf.generateCertificate(bis); > > certList.add(certificate); > > Store certs = new JcaCertStore(certList); > CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); > > Scanner s = new Scanner(System.in); > > //wait for the user to enter base64 encoded signed value. > final String signedHash = s.nextLine(); > System.out.println("Signing. . . "); > s.close(); > ContentSigner nonSigner = new ContentSigner() { > > @Override > public byte[] getSignature() { > return Base64.decodeBase64(signedHash); > } > > @Override > public OutputStream getOutputStream() { > return new ByteArrayOutputStream(); > } > > @Override > public AlgorithmIdentifier getAlgorithmIdentifier() { > return new > DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption"); > } > }; > org.bouncycastle.asn1.x509.Certificate cert = > org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(certificate.getEncoded())); > JcaSignerInfoGeneratorBuilder sigb = new > JcaSignerInfoGeneratorBuilder(new > JcaDigestCalculatorProviderBuilder().build()); > sigb.setDirectSignature(true); > gen.addCertificates(certs); > gen.addSignerInfoGenerator(sigb.build(nonSigner, new > X509CertificateHolder(cert))); > > CMSTypedDataInputStream msg = new CMSTypedDataInputStream(new > ByteArrayInputStream(dataToDigest)); // this is never used. > CMSSignedData signedData = gen.generate(msg, false); > byte[] pkcs7 = signedData.getEncoded(); > > //this is the signature. > content.close(); > return pkcs7; > } catch (CertificateException e) { > content.close(); > e.printStackTrace(); > } catch (CMSException e) { > content.close(); > e.printStackTrace(); > } catch (OperatorCreationException e) { > content.close(); > e.printStackTrace(); > } catch (NoSuchAlgorithmException e) { > content.close(); > e.printStackTrace(); > } > content.close(); > return null; > } > > static public int getMDPPermission(PDDocument doc) > { > COSBase base = > doc.getDocumentCatalog().getCOSObject().getDictionaryObject(COSName.PERMS); > if (base instanceof COSDictionary) > { > COSDictionary permsDict = (COSDictionary) base; > base = permsDict.getDictionaryObject(COSName.DOCMDP); > if (base instanceof COSDictionary) > { > COSDictionary signatureDict = (COSDictionary) base; > base = signatureDict.getDictionaryObject("Reference"); > if (base instanceof COSArray) > { > COSArray refArray = (COSArray) base; > for (int i = 0; i < refArray.size(); ++i) > { > base = refArray.getObject(i); > if (base instanceof COSDictionary) > { > COSDictionary sigRefDict = (COSDictionary) base; > if > (COSName.DOCMDP.equals(sigRefDict.getDictionaryObject("TransformMethod"))) > { > base = > sigRefDict.getDictionaryObject("TransformParams"); > if (base instanceof COSDictionary) > { > COSDictionary transformDict = > (COSDictionary) base; > int accessPermissions = > transformDict.getInt(COSName.P, 2); > if (accessPermissions < 1 || > accessPermissions > 3) > { > accessPermissions = 2; > } > return accessPermissions; > } > } > } > } > } > } > } > return 0; > } > > static public void setMDPPermission(PDDocument doc, PDSignature > signature, int accessPermissions) > { > COSDictionary sigDict = signature.getCOSObject(); > > // DocMDP specific stuff > COSDictionary transformParameters = new COSDictionary(); > transformParameters.setItem(COSName.TYPE, > COSName.getPDFName("TransformParams")); > transformParameters.setInt(COSName.P, accessPermissions); > transformParameters.setName(COSName.V, "1.2"); > transformParameters.setNeedToBeUpdated(true); > > COSDictionary referenceDict = new COSDictionary(); > referenceDict.setItem(COSName.TYPE, COSName.getPDFName("SigRef")); > referenceDict.setItem("TransformMethod", > COSName.getPDFName("DocMDP")); > referenceDict.setItem("DigestMethod", COSName.getPDFName("SHA1")); > referenceDict.setItem("TransformParams", transformParameters); > referenceDict.setNeedToBeUpdated(true); > > COSArray referenceArray = new COSArray(); > referenceArray.add(referenceDict); > sigDict.setItem("Reference", referenceArray); > referenceArray.setNeedToBeUpdated(true); > > // Catalog > COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject(); > COSDictionary permsDict = new COSDictionary(); > catalogDict.setItem(COSName.PERMS, permsDict); > permsDict.setItem(COSName.DOCMDP, signature); > catalogDict.setNeedToBeUpdated(true); > permsDict.setNeedToBeUpdated(true); > } > > } > > -- > Regards > Paresh Chouhan > https://github.com/pareshchouhan > -- Regards Paresh Chouhan https://github.com/pareshchouhan

