O projeto consiste de DispatchActions, Servlets e Applets.
Os testes foram feitos com o token EPASS2000 da Pronova. Onde o funcionamento depende de dois arquivos principais: pkcs11.cfg (arquivo de configuração) e ngp11v211.dll.
Aqui eu separo os elementos do projeto em várias partes, caso o link de download fique quebrado.
Applet
O módulo de applet está com as classes SignatureManager e TokenSignerApplet.
package br.com.certificacao; import java.applet.Applet; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Provider; import java.security.Security; import java.security.cert.Certificate; import java.util.Enumeration; /** * Classe responsável por gerenciar a assinatura contida em * um token. É necessário efetuar o login no dispositivo * antes de utilizá-lo. */ public class SignatureManager { private KeyStore keyStore; private char[] PIN; private final String CONFIG_DIR_NAME = "C:\\assinatura\\"; private final String CONFIG_FILE_NAME = "pkcs11.cfg"; private final String WINDOWS_DIR_NAME = "C:\\Windows\\System32\\"; private final String DLL_FILE_NAME = "ngp11v211.dll"; private String arquivoDLLApp = ""; public void createTokenFiles(String urlApp){ criarDiretorio(); criarDLL(urlApp); createConfigFile(); } private void criarDiretorio(){ try { File dir = new File(CONFIG_DIR_NAME); if (!dir.exists()){ dir.mkdirs(); } }catch (Exception e) { } } private void createConfigFile(){ try { File file = new File(CONFIG_DIR_NAME + CONFIG_FILE_NAME); file.createNewFile(); FileWriter writer = new FileWriter(file); writer.write("name = PKCS11\n"); writer.write("library = " + arquivoDLLApp + "\n"); writer.write("slot = 1\n"); writer.write("disabledMechanisms = {\n"); writer.write(" CKM_SHA1_RSA_PKCS\n"); writer.write("}\n"); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Metodo que verifica a situação da DLL do Token e-Pass da Pronova. * Verifica se a DLL já está criada na pasta Windows\System32. * Se o arquivo não existir, cria uma cópia na pasta apontada pela variável CONFIG_DIR_NAME. * * @param urlApp */ private void criarDLL(String urlApp){ try { File dllWindows = new File(WINDOWS_DIR_NAME + DLL_FILE_NAME); if (dllWindows.exists()){ arquivoDLLApp = WINDOWS_DIR_NAME + DLL_FILE_NAME; }else{ arquivoDLLApp = CONFIG_DIR_NAME + DLL_FILE_NAME; URL url = new URL(urlApp+ "DLLServlet"); HttpURLConnection connectionGet = (HttpURLConnection) url.openConnection(); connectionGet.setRequestProperty("Request-Method", "GET"); connectionGet.setDoInput(true); connectionGet.setDoOutput(false); connectionGet.connect(); // imprime o numero do resultado System.out.println("Resultado: "+ connectionGet.getResponseCode()+ "/"+ connectionGet.getResponseMessage()); InputStream input = connectionGet.getInputStream(); File file = new File(arquivoDLLApp); file.createNewFile(); FileOutputStream output = new FileOutputStream(file); int b = 0; while (-1 != ((b = input.read()))) { output.write(b); } output.close(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * @param password representa o PIN informado pelo usuário * @return true se houve sucesso no login ou false se ocorreu * algum erro */ public boolean login(String password, Applet applet)throws Exception{ boolean status = true; try { PIN = password.toCharArray(); //A configuracao do provider eh possivel diante de tres opcoes: //1.Configuracao do java.security // p. ex:security.provider.10=sun.security.pkcs11.SunPKCS11 D:\\desenvolvimento\\projetos\\funasa\\pkcs11.cfg //2.Pedindo para o usuario fornecer o arquivo de configuracao, através de um uploader (argh!!) //3.Manualmente, no codigo da applet //Foi escolhida a forma manual. Provider p = new sun.security.pkcs11.SunPKCS11(CONFIG_DIR_NAME + CONFIG_FILE_NAME); Security.addProvider(p); keyStore = KeyStore.getInstance("PKCS11"); keyStore.load(null, PIN); }catch(Exception e){ throw new Exception(e); } return status; } /** * @return chain representa o certificado armazenado no token */ public Certificate[] getCertificate(){ Certificate[] chain = null; try { EnumerationaliasesEnum = keyStore.aliases(); while (aliasesEnum.hasMoreElements()){ String alias = aliasesEnum.nextElement(); if(keyStore.isKeyEntry(alias)){ chain = keyStore.getCertificateChain(alias); break; } } } catch (GeneralSecurityException e) { e.printStackTrace(); } return chain; } /** * @return privateKey representa a chave privada armazenada no Token */ public PrivateKey getPrivateKey() { PrivateKey privateKey = null; try { Enumeration aliasesEnum = keyStore.aliases(); while (aliasesEnum.hasMoreElements()){ String alias = aliasesEnum.nextElement(); if(keyStore.isKeyEntry(alias)){ privateKey = (PrivateKey)keyStore.getKey(alias, PIN); keyStore.getCertificateChain(alias); break; } } } catch (GeneralSecurityException e) { e.printStackTrace(); } return privateKey; } }
package br.com.certificacao; import java.applet.Applet; import java.awt.Button; import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Calendar; import javax.swing.JOptionPane; import netscape.javascript.JSObject; import com.lowagie.text.pdf.PdfReader; import com.lowagie.text.pdf.PdfSignatureAppearance; import com.lowagie.text.pdf.PdfStamper; public class TokenSignerApplet extends Applet { private static final long serialVersionUID = 4023705496194390163L; private static final String PIN_CODE_PARAM = "pinCodeField"; private static final String NOME_PARAM = "nomeField"; private static final String APP_URL_PARAM = "appUrlField"; private static final String SIGN_BUTTON_CAPTION_PARAM = "signButtonCaption"; private Button mSignButton; private SignatureManager manager = new SignatureManager(); /** * Cria e inicializa a interface grafica da applet. */ public void init() { criarArquivoToken(); String signButtonCaption = this.getParameter(SIGN_BUTTON_CAPTION_PARAM); mSignButton = new Button(signButtonCaption); mSignButton.setLocation(0, 0); Dimension appletSize = this.getSize(); mSignButton.setSize(appletSize); mSignButton.setBackground(new Color(190, 1, 1)); mSignButton.setForeground(new Color(255,255,255)); mSignButton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { assinaStream(); } }); this.setLayout(null); this.add(mSignButton); } public void assinaStream(){ JSObject browserWindow = JSObject.getWindow(this); JSObject mainForm = (JSObject)browserWindow.eval("document.forms[0]"); String pinCodeParam = getParameter(PIN_CODE_PARAM); JSObject pinCodeField = (JSObject)mainForm.getMember(pinCodeParam); String pinCode = (String)pinCodeField.getMember("value"); String nomeParam = getParameter(NOME_PARAM); JSObject nomeField = (JSObject)mainForm.getMember(nomeParam); String nome = (String)nomeField.getMember("value"); String appParam = this.getParameter(APP_URL_PARAM); JSObject appField = (JSObject) mainForm.getMember(appParam); String appUrl = (String) appField.getMember("value"); try{ //Chama o servlet para leitura do pdf. String urlArquivoServlet = appUrl + "/ArquivoServlet"; URL url = new URL(urlArquivoServlet); HttpURLConnection connectionGet = (HttpURLConnection)url.openConnection(); connectionGet.setRequestProperty("Request-Method", "GET"); connectionGet.setDoInput(true); connectionGet.setDoOutput(false); connectionGet.connect(); //Stream de LEITURA de dados do pdf InputStream input = connectionGet.getInputStream(); SignatureManager manager = new SignatureManager(); if(manager.login(pinCode, this)){ java.security.PrivateKey key = manager.getPrivateKey(); java.security.cert.Certificate certificate[] = manager.getCertificate(); try{ PdfReader pdfReader = new PdfReader(input); //Monta a conexao com o Servlet para escrever o arquivo assinado digitalmente HttpURLConnection connectionPost = (HttpURLConnection)url.openConnection(); connectionPost.setRequestProperty("Content-Type", "application/pdf"); connectionPost.setDoOutput(true); connectionPost.connect(); OutputStream output = connectionPost.getOutputStream(); //Assina PDF PdfStamper stamper = PdfStamper.createSignature(pdfReader, output, '\0'); PdfSignatureAppearance signAppearance = stamper.getSignatureAppearance(); signAppearance.setCrypto(key, certificate, null, PdfSignatureAppearance.WINCER_SIGNED); signAppearance.setReason("Empresa S/A"); signAppearance.setLocation("Sao Paulo - SP"); signAppearance.setSignDate(Calendar.getInstance()); signAppearance.setCertificationLevel(1); stamper.close(); BufferedReader rd = new BufferedReader(new InputStreamReader(connectionPost.getInputStream())); String line; while((line = rd.readLine()) != null){ System.out.println(line); } output.close(); input.close(); rd.close(); //chamada de uma funcao javascript no jsp browserWindow.call("chamaAction", new Object[] {nome}); } catch(Exception e){ e.printStackTrace(); } } else{ JOptionPane.showMessageDialog(this, "Erro ao assinar documento."); } input.close(); } catch(Exception e) { e.printStackTrace(); } } private void criarArquivoToken(){ try { JSObject browserWindow = JSObject.getWindow(this); JSObject mainForm = (JSObject) browserWindow.eval("document.forms[0]"); String appParam = this.getParameter(APP_URL_PARAM); JSObject appField = (JSObject) mainForm.getMember(appParam); String app = (String) appField.getMember("value"); manager.createTokenFiles(app); }catch (Exception e) { e.printStackTrace(); } } }
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <html> <head> <title>Teste Token Signer Applet</title> </head> <body> <script type="text/javascript"> function chamaAction(tarefa){ var forward = "./certificacao.do?method=doFimCertificacao" + "&idTarefa=" + tarefa; doSubmit(forward); } </script> <form> <input type="hidden" name="appUrl" value=${pageContext.request.contextPath}> Código PIN: <input type="text" width="100" size=80 name="pinCode"> <br> Nome: <input type="text" width="100" size=80 name="nome" value=${nome_request}> </form> <object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" codebase="http://java.sun.com/update/1.5.0/jinstall-1_5-windows-i586.cab#Version=5,0,0,5" width="130" height="25" name="TokenSignerApplet"> <param name="code" value="br.com.certificacao.TokenSignerApplet"> <param name="archive" value="TokenSignerApplet.jar , ./certificacao/bcprov-jdk14-138.jar , ./certificacao/iText-2.0.8.jar"> <param name="mayscript" value="true"> <param name="type" value="application/x-java-applet;version=1.5"> <param name="scriptable" value="false"> <param name="signButtonCaption" value="Assinar Documento"> <param name="appUrlField" value="appUrl"> <param name="pinCodeField" value="pinCode"> <param name="nomeField" value="nome"> <comment> <embed type="application/x-java-applet;version=1.5" code="br.com.certificacao.TokenSignerApplet" archive="TokenSignerApplet.jar , ./certificacao/bcprov-jdk14-138.jar , ./certificacao/iText-2.0.8.jar" width="130" height="25" scriptable="true" pluginspage="http://java.sun.com/products/plugin/index.html#download" appUrlField="appUrl" pinCodeField="pinCode" signButtonCaption="Assinar Documento" nomeField="nome"> </embed> </comment> </object> </body> </html>
Servlet
package br.com.servlet; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet que gerencia o armazenamento do arquivo assinado. * * @author marcelo.garcia * */ public class ArquivoServlet extends HttpServlet { private static final long serialVersionUID = 4102000674926121528L; /** * Recupera um arquivo e envia para o cliente (por ex. um applet) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("ArquivoServlet - doGet"); lerArquivo(request, response); } /** * Recebe o arquivo assinado e grava em disco no servidor. */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("ArquivoServlet - doPost"); try { InputStream input = request.getInputStream(); File file = new File("D:\\temp\\ResumoServletCriado.pdf"); file.createNewFile(); FileOutputStream output = new FileOutputStream(file); int b = 0; while (-1 != ((b = input.read()))) { output.write(b); } output.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Recupera um arquivo pdf * * @param request * @param response * @throws ServletException * @throws IOException */ private void lerArquivo (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("ArquivoServlet - lerArquivo"); try { response.setContentType("application/pdf"); InputStream is = new FileInputStream("D:\\temp\\Resumo.pdf"); geraOutput(response, is); } catch (Exception e) { e.printStackTrace(); } } /** * Gera saída do arquivo PDF gerado * * @param response * @param is * @throws IOException */ private void geraOutput(HttpServletResponse response, InputStream is) throws IOException { BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); try { response.setCharacterEncoding("iso-8859-1"); int c; while ((c = is.read()) != -1) { byte[] b = new byte[1]; b[0] = (byte)c; out.write(b); } } finally { if (is != null) is.close(); if (out != null){ out.flush(); out.close(); } } } }
package br.com.servlet; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet que controla a DLL do token da pronova. * * @author marcelo.garcia */ public class DLLServlet extends HttpServlet { private static final long serialVersionUID = -3363073130063055864L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("DO-post"); try { InputStream url = this.getServletContext().getResourceAsStream("\\dll\\ngp11v211.dll"); OutputStream output = response.getOutputStream(); int b = 0; while (-1 != ((b = url.read()))) { output.write(b); } output.close(); url.close(); } catch (IOException e) { e.printStackTrace(); } } protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { doGet(request, response); } }
Struts
package br.com.struts; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.DispatchAction; /** * * @author marcelo.garcia * @version 1.0 */ public class ActionCertificacao extends DispatchAction { public ActionForward doInicioCertificacao(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { System.out.println("Metodo inicial da certificacao"); return mapping.findForward("token"); } /** * Metodo que finaliza o processo de assinatura digital. * Chamado por uma funcao javascript no token.jsp. * * @param mapping * @param actionForm * @param request * @param response * @return * @throws IOException * @throws ServletException */ public ActionForward doFimCertificacao(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { System.out.println("Metodo final da certificacao "); return null; } }
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">path="/certificacao" type="br.com.struts.ActionCertificacao" parameter="method" scope="request" name="formCertificacao" validate="false">
Olá Marcelo, o link de seu projeto no megadownload foi cancelado. Voce poderia postar em outro repositório ou enviar por email: saka84k@gmail.com. Obrigado.
ResponderExcluirOlá Marcelo,
ResponderExcluirGostaria de receber seu projeto por email (marlessonsa@gmail.com), pois o link está off.
gostaria de receber por email: jesse.oli@hotmail.com
ResponderExcluirOlá Marcelo,
ResponderExcluirPode enviar o seu projeto para meu e-mail por favor, desde já agradeço.
brancoisrael@gmail.com
pode me enviar seu projeto por e-mail marcelodevargas@gmail.com
ResponderExcluirobrigado
Código enviado por email.
ExcluirObrigado.
Este comentário foi removido pelo autor.
ExcluirMarcelo boa tarde, primeiro parábens pelo POST de um assunto dificil de se encontrar na NET. Os que achei foram muito limitados. Você poderia, por favor, enviar o projeto no meu email souzaaar@hotmail.com.br.
ResponderExcluirAntonio,
Excluirtentei enviar o email, mas deu erro.
Tem outro?
Opa Marcelo tem como me mandar os fonte desse projeto tbm? email: alessandrosilva@outlook.com
ResponderExcluirvlw...
cara tava procurando isso há algum tempo.
ResponderExcluirPoderia me enviar o fonte ivan.ojc88@gmail.com
Parabéns pelo excelente trabalho cara!
ResponderExcluirConsegue me enviar o projeto por e-mail?
Se possível também gostaria desses:
http://codigogenerico.blogspot.com.br/2010/07/codigo-de-applet-para-assinatura.html
Obrigado!
andre_r.carvalho@yahoo.com.br
Fala brother,
Excluirmandei o código. Caso ocorra algum problema, me avise.
Abs
Parabéns pelo ótimo artigo, mas você poderia me enviar o seu projeto para o meu e-mail? Desde já agradeço a atenção!
ResponderExcluirguilherme.wl@gmail.com
Bom dia Marcelo!
ResponderExcluirmuito interessante este artigo, será que você não poderia mandar no meu e-mail o projeto. pdrdomgi@gmail.com
Desde já agradeço!
Por gentileza poderia me mandar pelo email robsonsilveriosantos@gmail.com .
ResponderExcluirAchei muito bom artigo porém acredito que fica melhor entendido vendo em execução.
Obrigado
poderia me mandar pelo email leandrobortoli02@gmail.com
ResponderExcluirObrigado.
Este comentário foi removido pelo autor.
ResponderExcluirPode me passar o projeto por email, Marcelo?
ResponderExcluirdjheisonfernando@gmail.com
Obrigado.
Já pode pegar em https://github.com/mbgarcia/certificadoDigital.
ExcluirAbs
oi,
ResponderExcluirEste é o blog perfeito para quem quer saber sobre este tópico. Você sabe Você definitivamente colocar uma nova rodada sobre um assunto thats sido escrito sobre por anos. Grandes coisas, ótimo!
Thanks!
Assinatura digital pdf
Obrigado Carolina.
ExcluirSempre procuro compartilhar meus aprendizados. Realmente, esta foi uma solução muito solicitada.
Abs
Marcelo,
ResponderExcluirParabéns pelo Post.
Você sabe dizer se alguma maneira de assinarmos o PDF sem fazer o download dele para a máquina do usuário, ou seja, assinar o PDF no servidor, mas usando o TOKEN USB?
Olá Carlos,
ExcluirEntendo que a questão é o uso do certificado que, teoricamente, fica na máquina do usuário. Desta forma, a applet acessa o arquivo localmente.
Posso te responder isso, até onde conheço.
Abs
Boa Noite Marcelo, gostei muito do seu blog e principalmente do código, eu baixei ele no endereço acima e coloquei para rodar no eclipse só que está dando um erro na classe TokenSignerApplet no JSObject, segue o erro abaixo:
ResponderExcluirnetscape.javascript.JSException
at netscape.javascript.JSObject.getWindow(JSObject.java:144)
Se você souber como resolver este erro eu lhe agradeço muito.
Grato.
Sérgio Alex.
Sérgio,
Excluirnormalmente problemas com applets derivam de versão de Java. Está compilando com qual versão? Este erro acontece na execução da página? Qual o teu servidor?
Att
Ola Marcelo, estou compilando com o jdk 1.7.0, jre 7, estou rodando direto no eclipse e abre uma janela dizendo que o applet iniciou mas não abre no browser. Esta configurado o Tomcat na máquina e no elicpse.
ExcluirSergio, manda o stacktrace do erro. Olhando só esse trecho fica difícil ajudar mais.Tenta compilar em outra versão do Java tb
ExcluirMuito obrigado Marcelo pela ajuda, segue abaixo o erro que aparece no Eclipse.
ResponderExcluirnetscape.javascript.JSException
at netscape.javascript.JSObject.getWindow(JSObject.java:121)
at br.com.certificacao.TokenSignerApplet.criarArquivoToken(TokenSignerApplet.java:136)
at br.com.certificacao.TokenSignerApplet.init(TokenSignerApplet.java:42)
at sun.applet.AppletPanel.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Já instalei o JDK 1.8 e continuou o mesmo erro.
Cara que massa !!! poderia mandar esse codigo por email?
ResponderExcluirte agradeco muito.Parabéns pela iniciativa
Boa tarde,
Excluirpode baixar no link https://github.com/mbgarcia/certificadoDigital
Obrigado
Olá, Marcelo!
ResponderExcluirEstou começando a pesquisar sobre assinaturas digitais em java e me deparei com o seu blog. É possível enviar seu código para meu e-mail: brunokeleta@gmail?
Agradeço e parabéns pelo trabalho!
Att,
Bruno
Olá, Marcelo!
ResponderExcluirEstou começando a pesquisar sobre assinaturas digitais em java e me deparei com o seu blog. É possível enviar seu código para meu e-mail: brunokeleta@gmail?
Agradeço e parabéns pelo trabalho!
Att,
Bruno
Opa,
Excluirpode baixar no github:
https://github.com/mbgarcia/certificadoDigital
Abs
Marcelo, me interesso pelo seu trabalho sobre assinatura digital, pode entrar em contato para falarmos. itambsb@gmail.com, no aguardo.
ResponderExcluir