简体   繁体   English

在客户端/ Web服务器体系结构中实现数字签名

[英]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. 我正在尝试在Web应用程序中实现数字签名,这是Bruno Lowagie在白皮书中提供的示例。

4.3.3 Signing a document on the server using a signature created on the client 4.3.3使用在客户端上创建的签名在服务器上签名文档

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. 在此示例中,一切正常,但是当我们在签名后尝试打开pdf 时,签名验证过程中出现错误Error。 Error encountered while validating: Internal cryptographic library error. 验证时遇到错误:内部密码库错误。 Error Code: 0x2726 错误代码: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: 预签名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: 邮政标志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. 在这里,我要做的另一件事是,在发送证书到预签名servlet之前,我将证书链编码为base64

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 . 客户端代码在certChain检索仅签署者证书的证书链,将base64编码为strCertificate ,将其前缀为“ certChain =”并将该字符串放入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() ). 然后,它打开与PreSignservlet的连接,并使用中间的ByteArrayOutputStream byteStream以复杂的方式发送数据以进行发布(为什么不将postData.getBytes()简单地写入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. 因此,该servlet可能难以识别输入结束。 But that does not seem to be a current issue here. 但这似乎不是当前的问题。

Now you take the data returned by the servlet as is (ie without decoding) and feed it into signature creation. 现在,您按原样获取servlet返回的数据(即不解码),并将其输入到签名创建中。 Then you open a connection to the PostSignservlet to send the signature bytes to. 然后,打开与PostSignservlet的连接以将签名字节发送到。

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 ! 但是,现在您可以将签名(之前已发送的信息)(编码的证书)发送给该servlet,而不是签名数据,然后byteStream签名添加到本地byteStream

Why don't you simply write the signature in data to connection.getOutputStream() ? 为什么不简单地将签名写入dataconnection.getOutputStream()

Eventually you retrieve the output of the servlet as result PDF. 最终,您将servlet的输出作为结果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...", ie like your base64 encoded certificate. 您将证书而不是实际签名写入PostSignservlet将会解释为什么结果PDF中的CMS签名容器SignerInfo包含一个看起来像此“ certChain = MIIDKT ...”的“签名”值,即类似于您的base64编码证书。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM