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