segunda-feira, 17 de janeiro de 2011

Assinatura Digital de Documentos

Fiz este pequeno projeto para auxiliar na tarefa de assinatura digital de documentos.

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 {
   Enumeration aliasesEnum = 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">
            
   
     
 

35 comentários:

  1. 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.

    ResponderExcluir
  2. Olá Marcelo,

    Gostaria de receber seu projeto por email (marlessonsa@gmail.com), pois o link está off.


    ResponderExcluir
  3. gostaria de receber por email: jesse.oli@hotmail.com

    ResponderExcluir
  4. Olá Marcelo,

    Pode enviar o seu projeto para meu e-mail por favor, desde já agradeço.

    brancoisrael@gmail.com

    ResponderExcluir
  5. pode me enviar seu projeto por e-mail marcelodevargas@gmail.com

    obrigado

    ResponderExcluir
  6. Marcelo 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.

    ResponderExcluir
  7. Opa Marcelo tem como me mandar os fonte desse projeto tbm? email: alessandrosilva@outlook.com

    vlw...

    ResponderExcluir
  8. cara tava procurando isso há algum tempo.
    Poderia me enviar o fonte ivan.ojc88@gmail.com

    ResponderExcluir
  9. Parabéns pelo excelente trabalho cara!
    Consegue 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

    ResponderExcluir
    Respostas
    1. Fala brother,

      mandei o código. Caso ocorra algum problema, me avise.

      Abs

      Excluir
  10. 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!

    guilherme.wl@gmail.com

    ResponderExcluir
  11. Bom dia Marcelo!

    muito interessante este artigo, será que você não poderia mandar no meu e-mail o projeto. pdrdomgi@gmail.com

    Desde já agradeço!

    ResponderExcluir
  12. Por gentileza poderia me mandar pelo email robsonsilveriosantos@gmail.com .

    Achei muito bom artigo porém acredito que fica melhor entendido vendo em execução.

    Obrigado

    ResponderExcluir
  13. poderia me mandar pelo email leandrobortoli02@gmail.com

    Obrigado.

    ResponderExcluir
  14. Este comentário foi removido pelo autor.

    ResponderExcluir
  15. Pode me passar o projeto por email, Marcelo?
    djheisonfernando@gmail.com

    Obrigado.

    ResponderExcluir
    Respostas
    1. Já pode pegar em https://github.com/mbgarcia/certificadoDigital.

      Abs

      Excluir
  16. oi,
    Este é 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

    ResponderExcluir
    Respostas
    1. Obrigado Carolina.

      Sempre procuro compartilhar meus aprendizados. Realmente, esta foi uma solução muito solicitada.

      Abs

      Excluir
  17. Marcelo,

    Parabé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?

    ResponderExcluir
    Respostas
    1. Olá Carlos,

      Entendo 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

      Excluir
  18. 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:
    netscape.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.

    ResponderExcluir
    Respostas
    1. Sérgio,

      normalmente 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

      Excluir
    2. 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.

      Excluir
    3. Sergio, manda o stacktrace do erro. Olhando só esse trecho fica difícil ajudar mais.Tenta compilar em outra versão do Java tb

      Excluir
  19. Muito obrigado Marcelo pela ajuda, segue abaixo o erro que aparece no Eclipse.

    netscape.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.

    ResponderExcluir
  20. Cara que massa !!! poderia mandar esse codigo por email?
    te agradeco muito.Parabéns pela iniciativa

    ResponderExcluir
    Respostas
    1. Boa tarde,

      pode baixar no link https://github.com/mbgarcia/certificadoDigital

      Obrigado

      Excluir
  21. Olá, Marcelo!

    Estou 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

    ResponderExcluir
  22. Olá, Marcelo!

    Estou 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

    ResponderExcluir
    Respostas
    1. Opa,

      pode baixar no github:

      https://github.com/mbgarcia/certificadoDigital

      Abs

      Excluir
  23. Marcelo, me interesso pelo seu trabalho sobre assinatura digital, pode entrar em contato para falarmos. itambsb@gmail.com, no aguardo.

    ResponderExcluir