Question

enter image description here

Client=My application, Server=MSSP(Mobile Signature Services Provider)

Server is sign only the hash values.

Data to be sign:

*Base64 encoded SHA-1 digest of data to be signed. (28 characters)

*Base64 encoded SHA-256 digest of data to be signed. (44 characters)

*Base64 encoded SHA-384 digest of data to be signed. (64 characters)

*Base64 encoded SHA-512 digest of data to be signed. (88 characters)

*Base64 encoded DER encoded PKCS#1 DigestInfo to sign.

I want to external signature for pdf. I wrote the below code. But I get an error when opened the document with adobe.

Error:

modified or corrupted after the document is signed

Note: I use MSSP(Mobile Signature Service Provider) architecture. DataToBeSigned.length should be 44 for SHA256 algorithm.

My operation code:

  import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfDate;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignature;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.HashMap;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 *
 * @author murat.demir
 */
public class PdfSignOperation {

    private byte[] content = null;
    private X509Certificate x509Certificate;
    private PdfReader reader = null;
    private ByteArrayOutputStream baos = null;
    private PdfStamper stamper = null;
    private PdfSignatureAppearance sap = null;
    private PdfSignature dic = null;
    private HashMap<PdfName, Integer> exc = null;
    private ExternalDigest externalDigest = null;
    private PdfPKCS7 sgn = null;
    private InputStream data = null;
    private byte hash[] = null;
    private Calendar cal = null;
    private byte[] sh = null;
    private byte[] encodedSig = null;
    private byte[] paddedSig = null;
    private PdfDictionary dic2 = null;
    private X509Certificate[] chain = null;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public PdfSignOperation(byte[] content, X509Certificate cert) {
        this.content = content;
        this.x509Certificate = cert;
    }

    public byte[] getHash() throws Exception {
        reader = new PdfReader(new ByteArrayInputStream(content));

        baos = new ByteArrayOutputStream();
        stamper = PdfStamper.createSignature(reader, baos, '\0');
        sap = stamper.getSignatureAppearance();

        sap.setReason("Test");
        sap.setLocation("On a server!");
        sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
        sap.setCertificate(x509Certificate);

        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);

        exc = new HashMap<PdfName, Integer>();
        exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
        sap.preClose(exc);

        externalDigest = new ExternalDigest() {
            @Override
            public MessageDigest getMessageDigest(String hashAlgorithm)
                    throws GeneralSecurityException {
                return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
            }
        };

        chain = new X509Certificate[1];
        chain[0] = x509Certificate;

        sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
        data = sap.getRangeStream();
        cal = Calendar.getInstance();
        hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
        sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
        sh = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));
        return sh;
    }

    public String complateToSignature(byte[] signedHash) throws Exception {
        sgn.setExternalDigest(signedHash, null, "RSA");
        encodedSig = sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
        paddedSig = new byte[8192];

        System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
        dic2 = new PdfDictionary();

        dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
        sap.close(dic2);

        return Base64.encodeBytes(baos.toByteArray());
    }

}

My test code :

  public static void main(String[] args) throws Exception {
    TokenService.refreshAllTokens();
    String alias = "alias";
    String pin = "1234";
    TokenService.setAliasPin(alias, pin);


    File pdf = new File("E:/sample.pdf");
    FileInputStream is = new FileInputStream(pdf);
    byte[] content = new byte[is.available()];
    is.read(content);

    X509Certificate certificate = null;
    for (CertInfo certInfo : TokenService.getCertificates().values()) {
        if (certInfo.cert != null) {
            certificate = certInfo.cert;
        }
    }

    PdfSignOperation operation = new PdfSignOperation(content, certificate);

    byte[] hash = operation.getHash();

    //simule control for mobile signature.
    String encodeData = Base64.encodeBytes(hash);
    if (encodeData.length() != 44) {
        throw new Exception("Sign to data must be 44 characters");
    }

    // This function is local in the test run function (Simulated MSSP mobile signature)
    // return  a signed message digest
    byte[] signedData = TokenService.sign(encodeData, alias);

    //Combine signed hash value with pdf.
    System.out.println(operation.complateToSignature(signedData));
}

SignedPDF

Update:

I tried with the old library version and successful signature operation. My new code:

InputStream data = sap.getRangeStream();

X509Certificate[] chain = new X509Certificate[1];
chain[0] = userCert;

PdfPKCS7 sgn = new PdfPKCS7(null, chain, null, algorithm, null, false);

MessageDigest digest = MessageDigest.getInstance("SHA256", "BC");
byte[] buf = new byte[8192];
int n;
while ((n = data.read(buf, 0, buf.length)) > 0) {
    digest.update(buf, 0, n);
}
byte hash[] = digest.digest();
logger.info("PDF hash  created");
Calendar cal = Calendar.getInstance();

byte[] ocsp = null;
byte sh[] = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);

final String encode = Utils.base64Encode(sh);
SignatureService service = new SignatureService();

logger.info("PDF hash  sended to sign for web service");
MobileSignResponse signResponse = service.mobileSign(mobilePhone, signText, encode, timeout, algorithm, username, password, "profile2#sha256", signWsdl);

if (!signResponse.getStatusCode().equals("0")) {
    throw new Exception("Signing fails.\n" + signResponse.getStatusMessage());
}

byte[] signedHashValue = Utils.base64Decode(signResponse.getSignature());
sgn.setExternalDigest(signedHashValue, null, "RSA");

byte[] paddedSig = new byte[csize];


byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, null, ocsp);
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
if (csize + 2 < encodedSig.length) {
    throw new Exception("Not enough space for signature");
}

PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
sap.close(dic2);
logger.info("Signing successful");
Was it helpful?

Solution

In getHash you build the signed PDF (only the actual signature area is filled with '00' instead of signature bytes), calculate the hash of the byte range and return this hash.

In your main you sign this returned hash as is.

In complateToSignature you then insert this into the prepared PdfPKCS7 structure.

This is not correct, though: The hash which is signed in a PKCS7 / CMS signature is not the hash of the document (unless you have the most primitive form of a PKCS7 container) but the hash of the signed attributes (the hash of the document is but the value of only one of these attributes), also known as authenticated attributes.

Thus, you have to generate the signed attributes using the document hash you calculated, and then (hash and) sign this structure.

Have a look at iText's MakeSignature.signDetached and work it out in parallel:

String hashAlgorithm = externalSignature.getHashAlgorithm();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, externalDigest, false);
InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest(hashAlgorithm));
Calendar cal = Calendar.getInstance();
byte[] ocsp = null;
if (chain.length >= 2 && ocspClient != null) {
    ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
}
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp, crlBytes, sigtype);
byte[] extSignature = externalSignature.sign(sh);
sgn.setExternalDigest(extSignature, null, externalSignature.getEncryptionAlgorithm());

PS: When trying to transfer that code to your use case, please be aware of a major difference between the ExternalSignature method sign used here and your TokenService method sign:

ExternalSignature.sign is documented as:

/**
 * Signs it using the encryption algorithm in combination with
 * the digest algorithm.
 * @param message   the message you want to be hashed and signed
 * @return  a signed message digest
 * @throws GeneralSecurityException
 */
public byte[] sign(byte[] message) throws GeneralSecurityException;

So this method does both hash and sign.

In case of your method TokenService.sign, though, if you

use the sha256 algorithm, sign to data must be 44 characters

so the data you forward to that method seems to have to be already hashed (44 characters are needed for a base64 encoded SHA-256 hash value).

Thus, you have to calculate the hash of sh and forward this hash to TokenService.sign instead of the original signed attributes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top