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