java - 在客户端/Web 服务器架构中实现数字签名

标签 java pdf servlets itext digital-signature

我正尝试在 Web 应用程序中实现数字签名,如 Bruno Lowagie 在白皮书中提供的示例。

4.3.3 使用在客户端创建的签名在服务器上签署文档

预签名——客户端向服务器请求哈希。

后签名——客户端将签名的字节发送到服务器。

在此示例中一切正常,但是当我们在签名后尝试打开 pdf 时 它在签名验证期间出现错误错误。验证时遇到错误:内部加密库错误。错误代码:0x2726

这是我的代码:

客户:

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

预签名 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---------------");

后签名 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");  

这里我做了一件额外的事情,我在发送之前将证书链编码为 base64 预签名servlet。

最佳答案

您的代码有些困惑:

客户端代码在 certChain 中检索仅签署者证书的证书链,将其进行 base64 编码到 strCertificate 中,在其前面加上“certChain=”前缀并将该字符串放入进入 postData。然后它打开一个到 PreSignservlet 的连接,并使用中介 ByteArrayOutputStream byteStream 以复杂的方式发送数据以发布(你为什么不简单地写 postData. getBytes()connection.getOutputStream())。

不幸的是,您既没有关闭输出流也没有添加内容长度 header 。因此,servlet 可能难以识别输入结束。但这似乎不是当前的问题。

现在您按原样(即不解码)获取 servlet 返回的数据并将其提供给签名创建。然后您打开到 PostSignservlet 的连接以将签名字节发送到。

到目前为止它是有道理的。

但是您现在发送的不是签名数据,而是您之前已发送的信息(编码证书)到该 servlet,然后将签名添加到本地 byteStream!

为什么不直接将data中的签名写到connection.getOutputStream()中呢?

最终您检索 servlet 的输出作为结果 PDF。

您将证书而不是实际签名写入 PostSignservlet 将解释为什么结果 PDF 中的 CMS 签名容器 SignerInfo 包含一个“签名”值,看起来像这样“certChain=MIIDKT.. ”,即像您的 base64 编码证书。

关于java - 在客户端/Web 服务器架构中实现数字签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22189077/

相关文章:

java - 最接近Java中网页重新加载的等价物

mysql - 如何在Spring中同步数据库访问

jquery - Select2 不起作用,始终显示 "No results found"

java - 如何使用两个单独的存储库 (poms) 设置 Jenkins 构建?

java - 如何将 iText 作为源代码包含到我的项目中?

.net - 以编程方式搜索 PDF 文件中的文本并告知页码?

php - 写入现有的PDF文件,当用户直接调用.pdf扩展名时?

java - OpenJDK 9 IntelliJ IDEA (Ubuntu) "Can' t 解析符号”

java - 如何使 TeamCity 构建参数动态化?

java:如何获得我的带宽