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">