2
votes

I am trying to sign pdf document digitally and need to attach signature to signature panel using MSSP(mobile signature service provider). I researched some stackoverflow questions and i did things as below.

First i create checksum of pdf. Before generate the checksum add empty signature to pdf. After i generated the checksum i send that as data to sign document to the server. The server gives to me base64 signature and i found certificate chain from base64 signature. Now i need to attach signature to the pdf, show to the "Signature Panel" section of Adobe reader.

I extract certificate chain from base64 signature and i dont know how to attach this to pdf.

My code is :

this function does create empty signature to pdf.

public static void emptySignature(String src, String dest, String fieldname) throws IOException, DocumentException, GeneralSecurityException {
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
    ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    MakeSignature.signExternalContainer(appearance, external, 8192);
}

this function does get SHA-256 hash value of pdf.

public static String getHashValue(String filename) throws NoSuchAlgorithmException, IOException {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    String hex = checksum("output.pdf", md);
    System.out.println("CHECKSUM: " + hex);
    return hex;
}

private static String checksum(String filepath, MessageDigest md) throws IOException {

    try (DigestInputStream dis = new DigestInputStream(new FileInputStream(filepath), md)) {
        while (dis.read() != -1) ;
        md = dis.getMessageDigest();
    }

    StringBuilder result = new StringBuilder();
    for (byte b : md.digest()) {
        result.append(String.format("%02x", b));
    }
    return result.toString();
}

then i send the hash of pdf to server and got base 64 signature value: "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggAQEVEVTVAAAAACggDCCBhwwggQEoAMCAQIC ... NKodC346j0GKueTJ595rhi2NbT679XZwMaMMqEyT41pimV76Nm85eW/2yYjHt08gCNVSJGP7laR8taVAAAAAAAAA="

The certifcate chain looks like this: enter image description here

i tried some methods to attach signature to pdf's Signature panel but it requires private key. So please helping me giving for some advise, Thank you.

UPDATE 1:

After i attach signature to pdf using public certificate i got message in pdf "signature is invalid" enter image description here

This code is how i attach signature(I generated pem file from first certificate of chain):

final String SRC = "test.pdf";
final String DEST = "signed.pdf";
final String CERT = "cert.pem";

    File initialFile = new File(CERT);
    InputStream is = new FileInputStream(initialFile);

    // We get the self-signed certificate from the client
    CertificateFactory factory = CertificateFactory.getInstance("X.509");
    Certificate[] chain = new Certificate[1];
    chain[0] = factory.generateCertificate(is);
    System.out.println("chain[0]: -----> " + chain[0]);

    // we create a reader and a stamper
    PdfReader reader = new PdfReader(SRC);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');

    // we create the signature appearance
    PdfSignatureAppearance sap = stamper.getSignatureAppearance();
    sap.setReason("test");
    sap.setLocation("test");
    sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "signature"); //invisible
    sap.setCertificate(chain[0]);

    // we create the signature infrastructure
    PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    dic.setReason(sap.getReason());
    dic.setLocation(sap.getLocation());
    dic.setContact(sap.getContact());
    dic.setDate(new PdfDate(sap.getSignDate()));
    sap.setCryptoDictionary(dic);
    HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
    exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
    sap.preClose(exc);
    ExternalDigest externalDigest = new ExternalDigest() {
        public MessageDigest getMessageDigest(String hashAlgorithm)
                throws GeneralSecurityException {
            return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
        }
    };
    PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
    InputStream data = sap.getRangeStream();
    byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));


    // we get OCSP and CRL for the cert
    OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
    OcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
    byte[] ocsp = null;
    if (chain.length >= 2 && ocspClient != null) {
        ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
    }

    byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
    byte[] signedAttributesHash = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));

    ByteArrayOutputStream os = baos;

    byte[] signedHash = java.util.Base64.getDecoder().decode(base64Signature);

    // we complete the PDF signing process
    sgn.setExternalDigest(signedHash, null, "RSA");
    Collection<byte[]> crlBytes = null;
    TSAClientBouncyCastle tsaClient = null;

    byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
    byte[] paddedSig = new byte[8192];
    System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
    PdfDictionary dic2 = new PdfDictionary();
    dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

    try {
        sap.close(dic2);
    } catch (DocumentException e) {
        throw new IOException(e);
    }

    FileOutputStream fos = new FileOutputStream(new File(DEST));
    os.writeTo(fos);

UPDATE 2:

public byte[] sign(byte[] message) throws GeneralSecurityException {

    MessageDigest messageDigest = MessageDigest.getInstance(getHashAlgorithm());
    byte[] messageHash  = messageDigest.digest(message);
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < messageHash.length; ++i) {
        sb.append(Integer.toHexString((messageHash[i] & 0xFF) | 0x100).substring(1, 3));
    }

    byte[] signedByte = null;
    String msisdn = "97688888888";
    Client client = null;
    try {
        client = new Client( msisdn, sb.toString());
    } catch (JSONException e) {
        e.printStackTrace();
    }
    try {
        String strResult = client.sendRequest();
        JSONObject jsonResult = new JSONObject(strResult);
        System.out.println("Response:" + jsonResult);
        String base64Signature = jsonResult.getJSONObject("MSS_SignatureResp").getJSONObject("MSS_Signature").getString("Base64Signature");

        System.out.println(base64Signature);

        signedByte = Base64.getDecoder().decode(base64Signature);
    } catch (IOException | JSONException e) {
        e.printStackTrace();
    }

    return signedByte;
}

UPDATE 3:

enter image description here

1
There are numerous errors in your approach, in particular you calculate your hash over the wrong bytes (you must not hash the whole prepared file but merely that prepared file except the placeholder to be replaced by the signature to inject), and you inject your signature into yet another signature field. Before going into details, please clarify what kind of signatures your signature server returns? Full-fledged CMS signature containers? Or naked signature values?mkl
@mkl thank you for your reply, the server returns base 64 encoded long string(in this case it was 7216 characters). i generated SignedData object from the b64 signature using bouncycastle SignedData. i am sorry i dont know much about "Full-fledged CMS signature containers? Or naked signature values?"Banzragch. B
i've found that the server returns to me CMSBanzragch. B
Great. That makes it easier to implement this. I'll try and write something up when I'm in office.mkl
One question first: Do you really need the process of PDF preparation and of signature container injection strictly separated? In my eyes that only is necessary if requesting the actual signature from your server takes long; in that case one does not want to block resources (in particular memory) for that long. If the signature is returned quickly (after at most a few seconds), a single-step approach is more apropos.mkl

1 Answers

1
votes

The code in the question is based on a multi-phase approach:

  • Sign the PDF injecting an empty byte array as signature container (by use of the ExternalBlankSignatureContainer).
  • Calculate the digest to sign (here incorrectly a digest for the whole file from the previous step is calculated, the digest must be calculated for the whole file except the signature container placeholder).
  • Request signature container for that document hash.
  • Inject signature container into the placeholder in the file from the first step.

If the signature server can take a long time to return a signature container, such an approach is appropriate but in the case at hand comments clarify

The server gives response quickly(just few seconds)

In such a use case one should proceed in a single step approach (as far as iText signing API calls are concerned):

PdfReader reader = new PdfReader(...);
PdfStamper stamper = PdfStamper.createSignature(reader, ..., '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "Signature");
ExternalSignatureContainer external = new RemoteSignatureContainer();
MakeSignature.signExternalContainer(appearance, external, 8192);

with a custom ExternalSignatureContainer implementation RemoteSignatureContainer:

class RemoteSignatureContainer implements ExternalSignatureContainer {

    @Override
    public byte[] sign(InputStream data) throws GeneralSecurityException {
        [return a CMS signature container signing the data from the InputStream argument]
    }

    @Override
    public void modifySigningDictionary(PdfDictionary signDic) {
        signDic.put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
        signDic.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
    }
}

I don't know your API to access your signature server but based on your UPDATE 2 I assume the sign method in your RemoteSignatureContainer would look like this:

@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
    MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
    byte[] messageHash  = messageDigest.digest(StreamUtil.inputStreamToArray(data));
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < messageHash.length; ++i) {
        sb.append(Integer.toHexString((messageHash[i] & 0xFF) | 0x100).substring(1, 3));
    }

    String msisdn = "97688888888";
    try {
        Client client = new Client(msisdn, sb.toString());

        String strResult = client.sendRequest();
        JSONObject jsonResult = new JSONObject(strResult);
        String base64Signature = jsonResult
                   .getJSONObject("MSS_SignatureResp")
                   .getJSONObject("MSS_Signature")
                   .getString("Base64Signature");

        return Base64.getDecoder().decode(base64Signature);
    } catch (IOException | JSONException e) {
        throw new GeneralSecurityException(e);
    }
}