Anuj Anuj - 14 days ago 11
Java Question

Implement Digital Signature in Client /Web Server Architecture

I am trying to implement digital signature in web application as example provided by by Bruno Lowagie in White Paper.

4.3.3 Signing a document on the server using a signature created on the client

Pre-signing— the client asks the server for a hash.

Post-signing— the client sends the signed bytes to the server.

every thing is working fine in this example but when we are try to open pdf after signing
it is giving an error Error during signature verification. Error encountered while validating: Internal cryptographic library error. Error Code: 0x2726

Here is my code:

client:

KeyStore eks = loadKeyStoreFromSmartCard("abc@123");

// Check if X.509 certification chain is available
Certificate[] certChain = new X509Certificate[1];
certChain[0] = getcert_eToken(null, eks);

String strCertificate = encodeX509CertChainToBase64(certChain);


ByteArrayOutputStream byteStream = new ByteArrayOutputStream(8192);
PrintWriter out = new PrintWriter(byteStream, true);

String postData = "certChain=" + strCertificate;

try {

HttpURLConnection connection = null;
URL dataURL = null;

dataURL = new URL("http://localhost:8085/Digital-Server/PreSignservlet");

connection = (HttpURLConnection) dataURL.openConnection();
connection.setRequestProperty("User-Agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows 2000)");
connection.setFollowRedirects(true);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setAllowUserInteraction(false);
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Language", "en-US");
connection.setRequestProperty("Cookie", cookie);
connection.connect();

out.print(postData);
out.flush();
out.close();

byteStream.writeTo(connection.getOutputStream());
InputStream in = connection.getInputStream();


ByteArrayOutputStream baos = new ByteArrayOutputStream();
int read;
byte[] data = new byte[256];

while ((read = in.read(data)) != -1) {
baos.write(data, 0, read);
}

byte[] hash = baos.toByteArray();

PrivateKey privateKey = getprivate_eToken(null, eks);

// we sign the bytes received from the server
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(hash);
data = sig.sign();

// --------------------------------------------
connection.disconnect();
in.close();

//Calling Post Sign Servelet
dataURL = new URL("http://localhost:8085/Digital-Server/PostSignservlet");
connection = (HttpURLConnection) dataURL.openConnection();

connection.setRequestProperty("User-Agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows 2000)");
connection.setFollowRedirects(true);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setAllowUserInteraction(false);
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Language", "en-US");
connection.setRequestProperty("Cookie", cookie);
connection.connect();
out.flush();
out.close();

byteStream.writeTo(connection.getOutputStream());
byteStream.write(data);

in = connection.getInputStream();

OutputStream outputStream = new FileOutputStream(
"D:\\Digital Signature\\Digital-Server\\WebContent\\WEB-INF\\result\\jaihanuman.pdf");

// int read = 0;
byte[] bytes = new byte[8192];

while ((read = in.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}

System.out.println("Done!");


presign servlet:

Certificate[] chain = decodeX509CertChainToBase64(cert);

// we create a reader and a stamper

ServletContext context = getServletContext();
String fullPath = context.getRealPath("/WEB-INF/result/hello.pdf");

PdfReader reader = new PdfReader(fullPath);
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("On a server!");
sap.setVisibleSignature(new Rectangle(72,737,400,780), 1, "sig");
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"));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);

// We store the objects we'll need for post signing in a session
HttpSession session = req.getSession(true);
session.setAttribute("sgn", sgn);
session.setAttribute("hash", hash);
session.setAttribute("cal", cal);
session.setAttribute("sap", sap);
session.setAttribute("baos", baos);


// we write the hash that needs to be signed to the HttpResponse output
OutputStream os = resp.getOutputStream();
os.write(sh, 0, sh.length);
os.flush();
os.close();
}
catch(Exception ex)
{
ex.printStackTrace();
}
System.out.println("end of pre sign servelet---------------");


post sign servlet:

try
{
// we get the objects we need for postsigning from the session
System.out.println("call post servelet1");
HttpSession session = req.getSession(false);

PdfPKCS7 sgn = (PdfPKCS7)session.getAttribute("sgn");
byte[] hash = (byte[])session.getAttribute("hash");
Calendar cal = (Calendar)session.getAttribute("cal");
PdfSignatureAppearance sap =(PdfSignatureAppearance) session.getAttribute("sap");
ByteArrayOutputStream os =(ByteArrayOutputStream) session.getAttribute("baos");
session.invalidate();

// we read the signed bytes

ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = req.getInputStream();

int read;
byte[] data = new byte[256];
while ((read = is.read(data, 0, data.length)) != -1) {
baos.write(data, 0, read);
}
// we complete the PDF signing process

sgn.setExternalDigest(baos.toByteArray(), null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CADES);
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));
sap.close(dic2);

// we write the signed document to the HttpResponse output stream

// let's write the file in memory to a file anyway
ServletContext context = getServletContext();
String fullPath = context.getRealPath("/WEB-INF/result/sign.pdf");

byte[] pdf = os.toByteArray();
OutputStream sos = resp.getOutputStream();
sos.write(pdf, 0, pdf.length);
sos.flush();
sos.close();

/*OutputStream sos = new FileOutputStream(fullPath);
os.writeTo(sos);
sos.flush();
sos.close();*/

}
catch(Exception ex)
{
ex.printStackTrace();
}

System.out.println("call post servelet2");


Here I am doing one extra thing, I am encoding certificate chain to base64 before sending
to presign servlet.

mkl mkl
Answer

Your code is somewhat confusing:

The client code retrieves a signer-certificate-only certificate chain in certChain, base64-encodes it into strCertificate, prefixes it with "certChain=" and puts that string into postData. Then it opens a connection to the PreSignservlet and sends the data to post in a complicated way using an intermediary ByteArrayOutputStream byteStream (why don't you simply write postData.getBytes() to the connection.getOutputStream()).

Unfortunately you neither close the output stream nor add a content length header. Thus, the servlet might have difficulties recognizing the end-of-input. But that does not seem to be a current issue here.

Now you take the data returned by the servlet as is (i.e. without decoding) and feed it into signature creation. Then you open a connection to the PostSignservlet to send the signature bytes to.

So far it makes sense.

But instead of the signature data you now send the information you already had sent before (the encoded certificate) to that servlet and afterwards add the signature to the local byteStream!

Why don't you simply write the signature in data to connection.getOutputStream()?

Eventually you retrieve the output of the servlet as result PDF.

Your writing the certificate instead of the actual signature to the PostSignservlet would explain why the CMS signature container SignerInfo in the result PDF contains a "signature" value which looks like this "certChain=MIIDKT...", i.e. like your base64 encoded certificate.