Demoiselle Certificate QuickStart Ednara Oliveira Emerson Oliveira Sobre o QuickStart - versão 2.0.0 ............................................................................................... v 1. Ambiente ......................................................................................................................... 1 1.1. Ambiente recomendado .............................................................................................. 1 2. Criação da aplicação .......................................................................................................... 3 2.1. Nossa primeira aplicação ............................................................................................ 3 2.2. Criando sua Applet para exibir informações em uma página ................................................. 3 2.3. Assinatura dos jars ................................................................................................... 4 2.4. Projeto Web Exemplo ................................................................................................ 5 2.5. Assinando um documento ........................................................................................... 7 2.6. Executando o Applet ................................................................................................ 11 3. Usando um carimbo de tempo ........................................................................................... 13 3.1. Como funciona ....................................................................................................... 13 3.2. Criando uma Servlet para gerar o TimeStamp ................................................................. 14 3.3. Criando sua própria implementação de TimeStampGenerator .............................................. 17 3.4. Adicionando segurança ............................................................................................. 21 3.4.1. Configurando o Jboss EAP 6 ........................................................................... 21 3.4.2. Configurando a aplicação Web ......................................................................... 21 3.4.3. Criando as páginas HTML ............................................................................... 22 3.4.4. Configurando o Servlet ................................................................................... 23 A. Repositório dos JARs ......................................................................................................... 25 B. Assinando JARs com certificado auto-assinado ......................................................................... 27 B.1. Criando um certificado ............................................................................................. 27 B.2. Assinando um jar com certificado auto-assinado .............................................................. 27 iii iv Sobre o QuickStart - versão 2.0.0 Este documento é um tutorial do tipo "passo-a-passo" que visa ilustrar de forma rápida e prática a criação de uma aplicação para assinatura digital utilizando o Demoiselle Certificate 2.0.0. Nota É desejável o conhecimento de algumas tecnologias envolvidas no desenvolvimento de aplicações Java Web, incluindo: • Linguagem Java • Java Applet • Servlets, JSP e Tag Libraries • HTML e XML • Contêineres e Servidores Web Nota Esta documentação refere-se à versão 2.0.0 do Demoiselle Certificate e pode diferir significativamente de outras versões. v vi Ambiente 1.1. Ambiente recomendado Para desenvolver e executar as aplicações deste guia, utilizamos e recomendamos o seguinte ambiente: Software Versão Site (Download) Java Development Kit (JDK) 7.0 www.oracle.com [http://www.oracle.com/ technetwork/java/javase/downloads/ index.html] Apache Maven 2.2 maven.apache.org [http:// maven.apache.org/docs/2.2.1/releasenotes.html] Eclipse IDE 4.3 www.eclipse.org [https://www.eclipse.org/ kepler/] m2eclipse plugin 1.4 m2eclipse.sonatype.org [http:// m2eclipse.sonatype.org/installingm2eclipse.html] JBoss Application Server 7.1.1 www.jboss.org [http://download.jboss.org/ jbossas/7.1/jboss-as-7.1.1.Final/jbossas-7.1.1.Final.zip] 1 2 Criação da aplicação 2.1. Nossa primeira aplicação Nesta seção apresentaremos o passo-a-passo para construção de um projeto de exemplo do demoiselle-applet. Nele será construída uma página html que executará a applet para assinatura de documentos, utilizando certificados A1 ou A3, e apresentação das informações do certificado na própria página html. 2.2. Criando sua Applet para exibir informações em uma página Crie um novo projeto Maven em branco, marcando a opção "Create a Simple Project" e atribua os seguintes valores: Group-id: br.gov.frameworkdemoiselle.certificate.sample Artifact-id: AppletCustomizada Packaging: JAR Após criada a aplicação adicione como dependência o componente demoiselle-certificate-applet: <dependencies> <dependency> <groupId>br.gov.frameworkdemoiselle.component</groupId> <artifactId>demoiselle-certificate-applet</artifactId> <version>2.0.0</version> </dependency> ... </dependencies> Como esse componente não está no repositório Maven, devemos adicionar também no pom.xml o repositório do qual o componente deve ser baixado: <repositories> <repository> <id>demoiselle</id> <name>Demoiselle SourceForge Repository</name> <url>http://demoiselle.sourceforge.net/repository/release</url> </repository> ... </repositories> Em seguida, crie a classe App.java no br.gov.frameworkdemoiselle.certificate.sample.applet, estendendo AbstractAppletExecute. pacote a classe 3 Capítulo 2. Criação da aplicação public class App extends AbstractAppletExecute { @Override public void execute(KeyStore keystore, String alias, Applet applet) { try { /* Exibe alguns dados do certificado */ ICPBrasilCertificate certificado = super.getICPBrasilCertificate(keystore, alias, false); AbstractAppletExecute.setFormField(applet, "mainForm", "cpf", certificado.getCpf()); AbstractAppletExecute.setFormField(applet, "mainForm", "nome", certificado.getNome()); AbstractAppletExecute.setFormField(applet, "mainForm", "nascimento", certificado.getDataNascimento()) AbstractAppletExecute.setFormField(applet, "mainForm", "email", certificado.getEmail()); } catch (KeyStoreException e) { JOptionPane.showMessageDialog(applet, e.getMessage(), "Error", } JOptionPane.ERROR_MESSAGE); } @Override public void cancel(KeyStore keystore, String alias, Applet applet) { /* Seu codigo customizado aqui... */ } } No código acima o método execute será acionado logo após o carregamento do keystore do usuário. O método getICPBrasilCertificate retorna um objeto do tipo ICPBrasilCertificate que possui todas as informações de um certificado ICPBrasil. Os métodos setFormField escrevem no formulário html chamado de mainForm no qual a applet está sendo executado. O terceiro parâmetro do método informa em qual campo do formulário a informação será registrada. O método cancel pode ser utilizado para implementar uma ação no caso do usuário desistir da ação. No código de exemplo é feito apenas a ocultação da applet. 2.3. Assinatura dos jars O modelo de segurança da plataforma Java é centrado sobre o conceito de sandbox (caixa de areia), no qual um código remoto como um applet por padrão não é confiável e, portanto, não pode ter acesso ilimitado ao Sistema Operacional. Para que possamos executar nossa applet, precisamos assinar todos os jars necessários à sua execução, conforme mostrado na tabela abaixo: Tabela 2.1. Lista dos jars assinados Jar Original Jar Assinado demoiselle-certificate-applet-customizada-1.0.0.jar demoiselle-certificate-applet-customizada-1.0.0assinado.jar demoiselle-certificate-applet-2.0.0.jar demoiselle-certificate-applet-2.0.0-assinado.jar demoiselle-certificate-core-2.0.0.jar demoiselle-certificate-core-2.0.0-assinado.jar demoiselle-certificate-signer-2.0.0.jar demoiselle-certificate-signer-2.0.0-assinado.jar demoiselle-certificate-policy-engine-2.0.0.jar demoiselle-certificate-policy-engine-2.0.0-assinado.jar demoiselle-certificate-timestamp-2.0.0.jar demoiselle-certificate-timestamp-2.0.0-assinado.jar demoiselle-certificate-criptography-2.0.0.jar demoiselle-certificate-criptography-2.0.0-assinado.jar 4 Projeto Web Exemplo Jar Original Jar Assinado demoiselle-certificate-ca-icpbrasil-2.0.0.jar demoiselle-certificate-ca-icpbrasil-2.0.0-assinado.jar demoiselle-certificate-ca-icpbrasil- demoiselle-certificate-ca-icpbrasil-homologacao-2.0.0- homologacao-2.0.0.jar assinado.jar bcprov-jdk15on-1.51.jar bcprov-jdk15on-1.51-assinado.jar bcpkix-jdk15on-1.51.jar bcpkix-jdk15on-1.51-assinado.jar bcmail-jdk15on-1.51.jar bcmail-jdk15on-1.51-assinado.jar commons-io-1.3.2.jar commons-io-1.3.2-assinado.jar log4j-1.2.17.jar log4j-1.2.17-assinado.jar slf4j-api-1.6.1.jar slf4j-api-1.6.1-assinado.jar slf4j-log4j12-1.6.1.jar slf4j-log4j12-1.6.1-assinado.jar plugin.jar plugin-assinado.jar Para mais detalhes sobre os procedimentos para assinatura dos artefatos, consulte a documentação de referência [http://demoiselle.sourceforge.net/docs/components/certificate/reference/2.0.0/html_single/], e o Apendice B. 2.4. Projeto Web Exemplo Crie um novo projeto Maven em branco, marcando a opção "Create a Simple Project" e atribua os seguintes valores: Group-id: br.gov.frameworkdemoiselle.certificate.sample Artifact-id: AppletCustomizadaWeb Packaging: WAR Adicione a página html que irá executar a applet: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Exemplo de Assinatura de Documento</title> <link href="css/default.css" rel="stylesheet"> </head> <body> <form id="mainForm" name="mainForm" method="post" action=""> <h3>Applet Exemplo de Assinatura Digital</h3> <object codetype="application/java" classid="java:br.gov.frameworkdemoiselle.certificate.applet.view.JPanelApplet" width=480 height=350 archive="AppletCustomizada-1.0.0-assinado.jar, demoiselle-certificate-applet-2.0.0-SNAPSHOT-assinado.jar, demoiselle-certificate-core-2.0.0-SNAPSHOT-assinado.jar, demoiselle-certificate-signer-2.0.0-SNAPSHOT-assinado.jar, demoiselle-certificate-policy-engine-2.0.0-SNAPSHOT-assinado.jar, demoiselle-certificate-timestamp-2.0.0-SNAPSHOT-assinado.jar, demoiselle-certificate-criptography-2.0.0-SNAPSHOT-assinado.jar, demoiselle-certificate-ca-icpbrasil-2.0.0-SNAPSHOT-assinado.jar, demoiselle-certificate-ca-icpbrasil-homologacao-2.0.0-SNAPSHOT-assinado.jar, bcprov-jdk15on-1.51-assinado.jar, 5 Capítulo 2. Criação da aplicação bcpkix-jdk15on-1.51-assinado.jar, bcmail-jdk15on-1.51-assinado.jar, commons-io-1.3.2-assinado.jar, log4j-1.2.17-assinado.jar, slf4j-api-1.6.1-assinado.jar, slf4j-log4j12-1.6.1-assinado.jar, plugin-assinado.jar"> <param name="factory.applet.action" value="br.gov.frameworkdemoiselle.certificate.sample.applet.App" /> <param name="applet.javascript.postaction.failure" value="foo" /> No Applet </object> <label for="documento">Arquivo: </label> <input id="documento" type="file" name="documento"> <label <input <label <input <label <input <label <input </form> </body> for="cpf">CPF</label> id="cpf" type="text" name="cpf" disabled="disabled"> for="nome">Nome</label> id="nome" type="text" name="nome" disabled="disabled"> for="nascimento">Nascimento</label> id="nascimento" type="text" name="nascimento" disabled="disabled"> for="email">Email</label> id="email" type="text" name="email" disabled="disabled"> </html> Nota É necessário que os arquivos .jar assinados na sessão anterior sejam adicionados ao mesmo diretório da página html que irá utilizá-los Para o funcionamento da appletapplet são necessárias as propiedades: factory.applet.action e applet.javascript.postaction.failure. A primeira define qual classe será instanciada no momento do clique do botão Ok e carregamento do Keystore do usuário, enquanto a segunda define qual método JavaScript deverá ser chamado em caso de alguma falha. Acrescente um arquivo default.css na pasta webpapp/css, com o seguinte conteúdo: label { display: block; margin-bottom: -1em; } input { display: block; position: relative; left: 7em; top: -0.5em; } input[type="file"]{ border: 1px solid #cccccc; padding: 5px; 6 Assinando um documento } O seu projeto ficará com a seguinte estrutura de diretórios: AppletCustomizadaWeb ### pom.xml ### src ### main # # # # ### java ### resources ### webapp ### css # # # # # ### ### ### ### default.css AppletCustomizada-1.0.0-assinado.jar applet.html bcmail-jdk15on-1.51-assinado.jar # # # # # # # ### ### ### ### ### ### ### bcpkix-jdk15on-1.51-assinado.jar bcprov-jdk15on-1.51-assinado.jar commons-io-1.3.2-assinado.jar demoiselle-certificate-applet-2.0.0-assinado.jar demoiselle-certificate-ca-icpbrasil-2.0.0-assinado.jar demoiselle-certificate-core-2.0.0-assinado.jar demoiselle-certificate-criptography-2.0.0-assinado.jar # ### # ### # ### # ### # ### # ### # ### ### test demoiselle-certificate-policy-engine-2.0.0-assinado.jar demoiselle-certificate-signer-2.0.0-assinado.jar demoiselle-certificate-timestamp-2.0.0-assinado.jar log4j-1.2.17-assinado.jar plugin-assinado.jar slf4j-api-1.6.1-assinado.jar slf4j-log4j12-1.6.1-assinado.jar ### java ### resources 2.5. Assinando um documento Para realizar assinatura digital do contéudo utilizamos o componente demoiselle-certificate-signer. Esse componente assina um conteúdo carregado no applet, utilizando um certificado digital também carregado pela applet. Para isso, devemos adicionar dependência à esse componente no pom.xml do projeto AppletCustomizada: <dependencies> <dependency> <groupId>br.gov.frameworkdemoiselle.component</groupId> <artifactId>demoiselle-certificate-signer</artifactId> <version>2.0.0</version> </dependency> ... 7 Capítulo 2. Criação da aplicação </dependencies> Para assinatura de documentos é necessário criar um objeto PKCS7Singer, configurar os atributos e chamar o método doSign(): PKCS7Signer signer = PKCS7Factory.getInstance().factoryDefault(); signer.setCertificates(keystore.getCertificateChain(alias)); signer.setPrivateKey((PrivateKey) keystore.getKey(alias, null)); byte[] signed = signer.doSign(content); signer.setSignaturePolicy(PolicyFactory.Policies.AD_RB_CADES_2_1); O componente implementa assinatura padrão ICPBrasil de referência básica (RB) e de referência temporal (RT). No método setSignaturePolicy() você pode definir a política a ser usada. Atualmente o componente suporta as política de assinatura listadas abaixo: • PolicyFactory.Policies.AD_RB_CADES_1_0, Refere-se à Assinatura Digital de Referência Básica versão 1.0; • PolicyFactory.Policies.AD_RB_CADES_1_1, Refere-se à Assinatura Digital de Referência Básica versão 1.1; • PolicyFactory.Policies.AD_RB_CADES_2_0, Refere-se à Assinatura Digital de Referência Básica versão 2.0; • PolicyFactory.Policies.AD_RB_CADES_2_1, Refere-se à Assinatura Digital de Referência Básica versão 2.1; • PolicyFactory.Policies.AD_RT_CADES_1_0, Refere-se à Assinatura Digital de Referência Temporal versão 1.0; • PolicyFactory.Policies.AD_RT_CADES_1_1, Refere-se à Assinatura Digital de Referência Temporal versão 1.1; • PolicyFactory.Policies.AD_RT_CADES_2_0, Refere-se à Assinatura Digital de Referência Temporal versão 2.0; • PolicyFactory.Policies.AD_RT_CADES_2_1, Refere-se à Assinatura Digital de Referência Temporal versão 2.1; Por isso, devemos adicionar ainda outra dependência ao projeto AppletCustomizada, justamente ao componente responsável pelo gerenciamento das políticas, o demoiselle-certificate-policy-engine: <dependencies> <dependency> <groupId>br.gov.frameworkdemoiselle.component</groupId> <artifactId>demoiselle-certificate-policy-engine</artifactId> <version>2.0.0</version> </dependency> ... </dependencies> 8 Assinando um documento Para utilizar esses recursos, devemos modificar nossa classe App.java, também no projeto AppletCustomizada, que ficará assim: public class App extends AbstractAppletExecute{ private static final Logger logger = LoggerFactory.getLogger(App.class); @Override public void execute(KeyStore keystore, String alias, Applet applet) { try { /* Exibe alguns dados do certificado */ ICPBrasilCertificate certificado = super.getICPBrasilCertificate(keystore, alias, false); AbstractAppletExecute.setFormField(applet, "mainForm", "cpf", certificado.getCpf()); AbstractAppletExecute.setFormField(applet, "mainForm", "nome", certificado.getNome()); AbstractAppletExecute.setFormField(applet, "mainForm", "nascimento", certificado.getDataNascimento()) AbstractAppletExecute.setFormField(applet, "mainForm", "email", certificado.getEmail()); /* Carregando o conteudo a ser assinado */ String documento = AbstractAppletExecute.getFormField(applet, "mainForm", "documento"); if (documento.length() == 0) { JOptionPane.showMessageDialog(applet, "Por favor, escolha um documento para assinar", "Error", JOptionPane.ERROR_MESSAGE); return; } String path = new File(documento).getAbsolutePath(); byte[] content = readContent(path); logger.info("Path.........: {}", path ); /* Parametrizando o objeto doSign */ PKCS7Signer signer = PKCS7Factory.getInstance().factoryDefault(); signer.setCertificates(keystore.getCertificateChain(alias)); signer.setPrivateKey((PrivateKey) keystore.getKey(alias, null)); signer.setAttached(true); signer.setSignaturePolicy(PolicyFactory.Policies.AD_RB_CADES_2_1); /* Realiza a assinatura do conteudo */ logger.info("Efetuando a assinatura do conteudo"); byte[] signed = signer.doSign(content); /* Grava o conteudo assinado no disco */ writeContent(signed, documento.concat(".p7s")); /* Valida o conteudo */ logger.info("Efetuando a validacao da assinatura."); boolean checked = signer.check(content, signed); if (checked) { JOptionPane.showMessageDialog(applet, "O arquivo foi assinado e validado com sucesso.", "Mensagem", JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(applet, inv#lida.", "Error", JOptionPane.ERROR_MESSAGE); "Assinatura } 9 Capítulo 2. Criação da aplicação } catch (KeyStoreException e) { JOptionPane.showMessageDialog(applet, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } catch (UnrecoverableKeyException e) { JOptionPane.showMessageDialog(applet, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } catch (NoSuchAlgorithmException e) { JOptionPane.showMessageDialog(applet, e.getMessage(), "Error", } } @Override public void cancel(KeyStore keystore, String alias, Applet applet) { // TODO Auto-generated method stub } private byte[] readContent(String arquivo) { byte[] result = null; try { File file = new File(arquivo); FileInputStream is = new FileInputStream(file); result = new byte[(int) file.length()]; is.read(result); is.close(); } catch (IOException ex) { logger.info(ex.getMessage()); } return result; } private void writeContent(byte[] conteudo, String arquivo) { try { File file = new File(arquivo); FileOutputStream os = new FileOutputStream(file); os.write(conteudo); os.flush(); os.close(); } catch (IOException ex) { logger.info(ex.getMessage()); } } } O seu projeto ficará com a seguinte estrutura de diretórios: AppletCustomizada ### pom.xml ### src ### main 10 # # ### java # ### br # # # # # # # # ### gov ### frameworkdemoiselle ### certificate ### sample JOptionPane.ERROR_MESSAGE); Executando o Applet # # # # ### applet ### App.java # ### resources ### test ### java ### resources 2.6. Executando o Applet Agora publique sua aplicação web em um servidor Jboss EAP6, e acesse a url http://localhost:8080/ AppletCustomizadaWeb/applet.html. Se o certificado digital necessitar do pin para que seja feito o acesso, a aplicação solicitará imediatamente o pin de seu certificado, conforme a tela abaixo: Figura 2.1. Solicitação de Pin do Certificado O componente exibirá uma tela com os certificados disponíveis, seu número de série, sua data inicial de validade, sua data final de validade, e o emissor deste certificado, fornecendo ao usuário a possibilidade de escolher qual certificado deseja-se utilizar. 11 Capítulo 2. Criação da aplicação Figura 2.2. Lista com certificado de usuário 12 Usando um carimbo de tempo 3.1. Como funciona Para usar uma política AD_RT (Assinatura Digital de Referência Temporal) do ICP-Brasil, precisamos requisitar uma referência temporal digital a uma ACT (Autoridade de Carimbo do Tempo), que possue um servidor devidamente homologado. O componente demoiselle-certificate-timestamp possui toda a implementação necessária para acessar o servidor de carimbo de tempo do SERPRO (que é uma ACT). Para obter uma referência temporal é necessário assinar uma requisição usando um certificado que esteja autorizado a acessar esse servidor, e assim gerar uma assinatura de conteúdo com referência temporal. Nota Para mais informações sobre o serviço de Assinatura Digital de Referência Temporal ofericido pela ACT SERPRO, acesse o site carimbodotempo.serpro.gov.br/act [http:// carimbodotempo.serpro.gov.br/act/]. Figura 3.1. Arquitetura padrão Para aplicações que disponibilizam assinatura com referência temporal para diversos usuários, essa implementação padrão pode não ser a ideal, pois será necessário autorizar cada usuário para obter um carimbo de tempo. Pensando nisso, o componente oferece possibilidade para que a aplicação não peça o carimbo de tempo diretamente ao servidor de carimbo de tempo, mas que solicite a um outro servidor que faça isso pela aplicação, como ilustra a figura 2.2. Dessa forma, apenas o servidor intermediário necessita assinar as requisições com um certificado autorizado pelo servirdor de carimbo de tempo, não havendo então a necessidade de autorizar cada usuário da aplicação. 13 Capítulo 3. Usando um carimbo... Figura 3.2. Arquitetura com Servlet Essa alternativa pode ser aplicada através da implementação, na própria aplicação, da classe que obtém o carimbo de tempo, em substituição à implementação padrão, como mostraremos a seguir. Em nossa aplicação vamos criar uma implementação de gerador de timestamp que ao invés de pedir ao servidor um carimbo usando o certificado que assina o documento, peça a um servidor que tenha acesso a um certificado de máquina devidamente autorizado. A assinatura do documento será feita pelo certificado do usuário que terá um carimbo de tempo para aquele conteúdo gerado com um certificado de aplicação. Nossa implementação irá enviar o conteúdo lido pela applet para um Servlet que irá carregar o certificado da aplicação e solicitar o carimbo de tempo. Nesse exemplo usamos Servlet, mas você também pode usar um serviço REST ou um WebService, lembrando apenas que é importante esse serviço exigir autenticação para que possa ser acessado. 3.2. Criando uma Servlet para gerar o TimeStamp Em nosso projeto web, vamos adicionar ao pom.xml a dependência do componente demoiselle- certificate-timestamp. Vamos utilizar sua API para obter um carimbo de tempo. <dependencies> <dependency> <groupId>br.gov.frameworkdemoiselle.component</groupId> <artifactId>demoiselle-certificate-timestamp</artifactId> <version>2.0.0</version> </dependency> ... </dependencies> Crie a classe TimeStampGeneratorServlet no pacote br.gov.frameworkdemoiselle.certificate.sample. Nessa classe vamos implementar um método para ler o certificado de máquina. Leremos o conteúdo enviado para a Servlet e faremos a requisição ao servidor de carimbo de tempo. 14 Criando uma Servlet para gerar o TimeStamp Para usar a API de Servlet do Java adicione a dependência ao javaee-web-api, no pom.xml da aplicação AppletCustomizadaWeb: <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> ... </dependencies> Além disso, nossa Servlet irá receber e enviar dados via stream. Por isso, adicionamos também dependência ao commons-io, que oferece algumas facilidades para a execução dessas tarefas: <dependencies> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency> ... </dependencies> Crie o método loadCertificate, que carrega o certificado no servidor da sua aplicação que tem acesso ao servidor de carimbo de tempo. Para mais detalhes de como implementar a leitura de certificado consulte a documentação de referência [http://demoiselle.sourceforge.net/docs/components/certificate/reference/2.0.0/ html_single/]. private void loadCertificate() throws Exception{ CertificateLoader certificateLoader = new CertificateLoaderImpl(); X509Certificate certificate = certificateLoader.load(new File("certificado.cer"); } O segundo passo é ler o arquivo enviado à nossa Servlet. content = IOUtils.toByteArray(request.getInputStream()); O terceito e último passo é enviar a solicitação para o servidor de carimbo de tempo usando a API do componente demoiselle-certificate-timestamp. Para criar a solicitação, basta criarmos um TimeStampOperator e chamarmos o método createRequest passando o conteúdo, o certificado e a chave privada para assinar a requisição. 15 Capítulo 3. Usando um carimbo... TimeStampOperator timeStampOperator = new TimeStampOperator(); byte[] reqTimestamp = timeStampOperator.createRequest(privateKey,certificates, content); timestamp = timeStampOperator.invoke(reqTimestamp); Abaixo segue um exemplo completo da classe. @WebServlet("/carimbo") public class TimeStampGeneratorServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(TimeStampGeneratorServlet.class); private PrivateKey privateKey = null; private Certificate[] certificates = null; @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOEx byte[] content; byte[] timestamp; try { loadCertificate(); //Lendo o Conte#do enviado content = IOUtils.toByteArray(request.getInputStream()); if (content.length > 0) { //requisitando um carimbo de tempo TimeStampOperator timeStampOperator = new TimeStampOperator(); byte[] reqTimestamp = timeStampOperator.createRequest(privateKey,certificates, content); timestamp = timeStampOperator.invoke(reqTimestamp); response.setContentType("application/octet-stream"); response.getOutputStream().write(timestamp); }else{ response.setContentType("text/plain"); response.setStatus(500); response.getOutputStream().write("Conte#do n#o enviado".getBytes()); } } catch (CertificateCoreException e) { response.setContentType("text/plain"); response.setStatus(500); response.getOutputStream().write(e.getMessage().getBytes()); } catch (Exception e) { response.setContentType("text/plain"); response.setStatus(500); response.getOutputStream().write("Erro ao fazer load do certificado habilitado para requisitar carimbo de tempo".getBytes()); } finally { response.getOutputStream().flush(); response.getOutputStream().close(); } 16 Criando sua própria implementação de TimeStampGenerator private void loadCertificate() throws Exception{ CertificateLoader certificateLoader = new CertificateLoaderImpl(); X509Certificate certificate = certificateLoader.load(new File("certificado.cer"); } } 3.3. Criando sua própria implementação de TimeStampGenerator Agora vamos criar uma implementação própria do TimeStampGenerator em nossa applet. Ao invés de solicitar um carimbo de tempo diretamente ao servidor, nossa implememtação se conecta a um serviço responsável por essa solicitação, enviando para ele o conteúdo, e recebendo de volta o carimbo de tempo. Assim, da mesma forma que a Servlet, vamos também adicionar aqui a dependência ao commons-io: <dependencies> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency> ... <dependencies> Em seu projeto AppletCustomizada, crie no pacote br.gov.frameworkdemoiselle.certificate.sample a classe MyTimeStampGeneratorImpl, que deve implementar a interface TimeStampGenerator. Para descobrir as implementações de TimeStampGenerator presentes no projeto, é utilizado o SPI, por isso precisamos fazer dois ajustes para que a classe criada seja utilizada no lugar da implementação padrão. A primeira é anotar a classe MyTimeStampGeneratorImpl com a anotação @Priority(Priority.MAX_PRIORITY), para definir a nossa implementação como prioritária. A segunda é criar um arquivo chamado br.gov.frameworkdemoiselle.certificate.timestamp.TimeStampGenerator no diretório src/main/resources/META-INF/services, e dentro desse arquivo colocar o caminho completo da classe MyTimestampGeneratorImpl, que é br.gov.frameworkdemoiselle.certificate.sample.MyTimeStampGeneratorImpl, no caso do nosso exemplo. Importante Para garantir que importante adicionar e adicionar o a sua a anotação caminho para implementação será utilizada, é @Priority(Priority.MAX_PRIORITY), sua implementação no arquivo br.gov.frameworkdemoiselle.certificate.timestamp.TimeStampGenerator, dentro da pasta src/main/resources/META-INF/services. Ao implementar a interface TimeStampGenerator, os seguintes métodos devem ser implementados: 17 Capítulo 3. Usando um carimbo... public void initialize(byte[] content, PrivateKey privateKey, Certificate[] certificates) throws CertificateCoreE } public void validateTimeStamp(byte[] content, byte[] response) throws CertificateCoreException { } public byte[] generateTimeStamp() throws CertificateCoreException { return null; } No método initialize vamos apenas atribuir o conteúdo this.content = content; O método validateTimeStamp vamos manter o mesmo código da implementação padrão: TimeStampOperator timeStampOperator = new TimeStampOperator(); timeStampOperator.validate(content, response); O método generateTimeStamp() é o método central da nossa implementação. Nele vamos conectar à servlet e enviar o conteúdo sobre o qual é requisitado o carimbo de tempo. Como em nossa aplicação usamos servlet, vamos começar estabelecendo a conexão com a URL e, a seguir, usar o método POST. URL url = new URL(urlString); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setUseCaches(false); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestProperty("Content-Type","application/octet-stream"); A urlString em nosso caso será: http://localhost:8080/AppletCustomizadaWeb/carimbo. Escrevemos o seguinte conteúdo no request: OutputStream os = connection.getOutputStream(); os.write(content); os.flush(); os.close(); Após estabelecidos os paramêtros da conexão, testamos os status HTTP que nos indica se a solicitação foi bem sucedida. O primeiro status que testamos é o 200. Esse status indica o sucesso na requisição, e assim poderemos ler no response o carimbo de tempo restornado. Esse mesmo carimbo será ainda validado pelo método validateTimeStamp com o conteúdo na instância local. Essa validação sempre ocorre, e, se houve qualquer alteração no arquivo ou carimbo, este é invalidado. int status = connection.getResponseCode(); if (status == 200) { if (connection.getContentType().equals("application/octet-stream")) { InputStream is = connection.getInputStream(); timestamp = IOUtils.toByteArray(is); 18 Criando sua própria implementação de TimeStampGenerator is.close(); } } Para os casos de erro, trataremos os status HTTP 500 - Internal Server Error, 401 - Unauthorized e 403 - Access to the requested resource has been denied. Abaixo, veja como ficará o código da nossa classe: @Priority(Priority.MAX_PRIORITY) public class MyTimestampGeneratorImpl implements TimeStampGenerator { private static final Logger logger = LoggerFactory.getLogger(MyTimestampGeneratorImpl.class); private byte[] content; public void initialize(byte[] content, PrivateKey privateKey, Certificate[] certificates) throws CertificateCoreException { this.content = content; } public byte[] generateTimeStamp() throws CertificateCoreException { byte[] timestamp = null; HttpURLConnection connection = null; try { // Cria a conexao com o servico que requisita o carimbo de Tempo URL url = new URL("http://localhost:8080/AppletCustomizadaWeb/carimbo"); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setUseCaches(false); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestProperty("Content-Type","application/octet-stream"); // Envia o conteudo OutputStream os = connection.getOutputStream(); os.write(content); os.flush(); os.close(); // Trata o status da conexao int status = connection.getResponseCode(); if (status == 200) { if (connection.getContentType().equals("application/octet-stream")) { InputStream is = connection.getInputStream(); timestamp = IOUtils.toByteArray(is); is.close(); } } if (status == 500) { if (connection.getContentType().equals("text/plain")) { String message = IOUtils.toString(connection.getErrorStream()); throw new CertificateCoreException(message); 19 Capítulo 3. Usando um carimbo... } } if (status == 403){ throw new CertificateCoreException("HTTP Status 403 - JBWEB000015: Access to the requested resource has been denied"); } if (status == 401){ throw new CertificateCoreException("HTTP Status 401"); } if (timestamp == null){ throw new CertificateCoreException("Carimbo de Tempo n#o foi gerado"); } } catch ( ConnectException e) { throw new CertificateCoreException("Erro ao conectar ao servico que solicita carimbo de tempo"); } catch ( IOException e) { throw new RuntimeException(e); } finally { if (connection != null) { connection.disconnect(); } } return timestamp; } public void validateTimeStamp(byte[] content, byte[] response) throws CertificateCoreException { TimeStampOperator timeStampOperator = new TimeStampOperator(); timeStampOperator.validate(content, response); } } Além disso, na classe App, devemos modificar a política que ajustamos para utilizar a política de Assinatura Digital de Referência Básica versão 2.1, para utilizar a política de Assinatura Digital de Referência Temporal versão 2.1: signer.setSignaturePolicy(PolicyFactory.Policies.AD_RT_CADES_2_1); A organização da AppletCustomizada ficara assim: AppletCustomizada ### pom.xml ### src ### main 20 # # ### java # ### br # # ### gov Adicionando segurança # # ### frameworkdemoiselle # # ### certificate # # # # # # ### App.java # # ### MyTimestampGeneratorImpl.java ### sample ### applet # ### resources ### test ### java ### resources 3.4. Adicionando segurança Por fim vamos adicionar segurança à nossa aplicação. É importante que o serviço disponível (seja usando Servlet, REST ou WebService) esteja sob o contexto de segurança da aplicação. Com isso, o serviço só poderá ser acessado pelo usuário autorizado pela aplicação. Em nosso exemplo usaremos a autenticação JAAS do tipo FORM em nosso Servidor Jboss EAP6. 3.4.1. Configurando o Jboss EAP 6 Acesse JBOSS_HOME/bin e execute o add-user crie um usuário de aplicação pertencente ao grupo admin, e adicione ao realm "ApplicationRealm". Para mais detalhes consulte a documentação add-user utility [https:// docs.jboss.org/author/display/AS71/add-user+utility]. 3.4.2. Configurando a aplicação Web Crie o arquivo web.xml na pasta webapp/WEB-INF do seu projeto AppletCustomizadaWeb. <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/webapp_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ web-app_3_0.xsd" version="3.0"> <security-constraint> <web-resource-collection> <web-resource-name>Admin</web-resource-name> <url-pattern>/applet.html</url-pattern> </web-resource-collection> <auth-constraint> <description>Only allow users from following roles</description> <role-name>admin</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.html</form-login-page> <form-error-page>/index.html</form-error-page> </form-login-config> </login-config> 21 Capítulo 3. Usando um carimbo... <security-role> <role-name>admin</role-name> </security-role> </web-app> 3.4.3. Criando as páginas HTML Crie em seu projeto Web AppletCustomizadaWeb as duas páginas abaixo. login.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Exemplo de Assinatura de Documento</title> <link href="css/default.css" rel="stylesheet"> </head> <body> <form id="form-login" method="post" action="j_security_check"> <div> <h2>Applet Exemplo de Assinatura Digital</h2> </div> <label for="username">Login</label> <input id="username" type="text" name="j_username"> <label for="password">Senha</label> <input id="password" type="password" name="j_password"> <button id="login" type="submit" value="Login">Entrar</button> </form> </body> </html> index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Exemplo de Assinatura de Documento</title> <link href="css/default.css" rel="stylesheet"> </head> <body> <div> <h2>Applet Exemplo de Assinatura Digital</h2> </div> <div> <a href="applet.html">Assinar Documento</a> </div> 22 Configurando o Servlet </body> </html> 3.4.4. Configurando o Servlet Adicione a anotação de sergurança à classe TimestampGeneratorServlet para que as definições de segurança sejam aplicadas. @ServletSecurity(value = @HttpConstraint(rolesAllowed = "admin")) Ao final o seu projeto ficará com a seguinte estrutura de diretórios: AppletCustomizadoWeb ### pom.xml ### src ### # # # # # # # # # # # # # # main ### java # ### br # ### gov # ### frameworkdemoiselle # ### certificate # ### sample # ### TimestampGeneratorServlet.java ### resources ### webapp ### css # ### default.css ### WEB-INF # ### web.xml ### applet.html # # # # # # ### ### ### ### ### ### AppletCustomizada-1.0.0-assinado.jar bcmail-jdk15on-1.51-assinado.jar bcpkix-jdk15on-1.51-assinado.jar bcprov-jdk15on-1.51-assinado.jar commons-io-1.3.2-assinado.jar demoiselle-certificate-applet-2.0.0-SNAPSHOT-assinado.jar # # ### demoiselle-certificate-ca-icpbrasil-2.0.0-SNAPSHOT-assinado.jar ### demoiselle-certificate-core-2.0.0-SNAPSHOT-assinado.jar # # ### demoiselle-certificate-criptography-2.0.0-SNAPSHOT-assinado.jar ### demoiselle-certificate-policy-engine-2.0.0-SNAPSHOT-assinado.jar # # ### demoiselle-certificate-signer-2.0.0-SNAPSHOT-assinado.jar ### demoiselle-certificate-timestamp-2.0.0-SNAPSHOT-assinado.jar # ### index.html # # ### log4j-1.2.17-assinado.jar ### login.html # # ### plugin-assinado.jar ### slf4j-api-1.6.1-assinado.jar # ### slf4j-log4j12-1.6.1-assinado.jar ### test ### java 23 Capítulo 3. Usando um carimbo... ### resources 24 Apêndice A. Repositório dos JARs Na tabela 2.1 listamos alguns arquivos .jar, que devem ser assinados para que a applet customizada funcione. Na tabela abaixo informamos onde podemos encontrar os arquivos requisitados. Tabela A.1. Repositório dos jars Arquivo Download demoiselle-certificate-*.jar Todos Demoiselle no seu os arquivos do Certificate podem repositório componente ser no obtidos Source Forge [http://demoiselle.sourceforge.net/repository/ release/br/gov/frameworkdemoiselle/component/] bcprov-jdk15on-1.51.jar http://search.maven.org [http://search.maven.org/ remotecontent?filepath=org/bouncycastle/bcprovjdk15on/1.51/bcprov-jdk15on-1.51.jar] bcpkix-jdk15on-1.51.jar http://search.maven.org [http://search.maven.org/ remotecontent?filepath=org/bouncycastle/bcpkixjdk15on/1.51/bcpkix-jdk15on-1.51.jar] bcmail-jdk15on-1.51.jar http://search.maven.org [http://search.maven.org/ remotecontent?filepath=org/bouncycastle/bcmailjdk15on/1.51/bcmail-jdk15on-1.51.jar] commons-io-1.3.2.jar http://search.maven.org [http://search.maven.org/ remotecontent?filepath=commons-io/commonsio/1.3.2/commons-io-1.3.2.jar] log4j-1.2.17.jar http://search.maven.org [http://search.maven.org/ remotecontent?filepath=log4j/log4j/1.2.17/ log4j-1.2.17.jar] slf4j-api-1.6.1.jar http://search.maven.org [http://search.maven.org/ remotecontent?filepath=org/slf4j/slf4j-api/1.6.1/slf4japi-1.6.1.jar] slf4j-log4j12-1.6.1.jar http://search.maven.org [http://search.maven.org/ remotecontent?filepath=org/slf4j/slf4j-log4j12/1.6.1/ slf4j-log4j12-1.6.1.jar] plugin.jar Esse arquivo é copiado de JAVA_HOME/jre/lib 25 26 Apêndice B. Assinando JARs com certificado auto-assinado No capítulo 2 comentamos que, por motivos de segurança, é preciso assinar todos os arquivos .jar necessários para a execução da applet. Para assinar esse tipo de arquivo é necessário um certificado para assinatura de código. Porém, é provável que os desenvolvedores não tenham esse tipo de certificado disponível para fazer testes locais antes de implementar determinado recurso em uma aplicação em produção, o que é uma prática comum. Por isso, nesse apêndice mostraremos como gerar um certificado que pode assinar arquivos .jar, e também como assiná-los. Enfatizamos que esse certificado só deve ser utilizado no ambiente de desenvolvimento, para testes e afins, e nunca em ambientes de produção. B.1. Criando um certificado Primeiramente criaremos o keystore que armazenará o certificado digital. A ferramenta keytool será utilizada para criação simultãnea do keystore e do certificado digital que identificaremos pelo alias applet_alias. keytool -genkey -alias applet_alias -keyalg RSA -keypass changeit -storepass changeit -keystore applet_keystore.jks Na sequência serão solicitadas algumas informações do certificado: What is your first and last name? [Unknown]: Framework Demoiselle What is the name of your organizational unit? [Unknown]: Demoiselle What is the name of your organization? [Unknown]: Demoiselle What is the name of your City or Locality? [Unknown]: Salvador What is the name of your State or Province? [Unknown]: BA What is the two-letter country code for this unit? [Unknown]: BR Is CN=Framework Demoiselle, OU=Demoiselle, O=Demoiselle, L=Salvador, ST=BA, C=BR correct? [no]: yes Será criado o keystore JKS de nome applet_keystore.jks que contém um certificado auto assinado. Seu par de chaves será identificado pelo alias applet_alias. B.2. Assinando um jar com certificado auto-assinado Neste momento a ferramenta jarsigner será utilizada para assinar todos os jars da aplicação. Portanto será necessário informar a localização do keystore, o nome do jar assinado, o nome do jar original e o alias do certificado: jarsigner -keystore applet_keystore.jks -signedjar meujar-assinado.jar meujar.jar applet_alias 27 Apêndice B. Assinando JARs co... Importante Note que o jar assinado (meujar-assinado.jar) define o nome do arquivo jar que será criado, diferente do nome original do jar (meujar.jar). Dentro do jar, na pasta META-INF, foram inseridos os aquivos APPLET_A.RSA, APPLET_A.SF e MANIFEST.MF, que possuem informações como o algoritmo de criptografia utilizado e a chave pública do certificado. Para verificar a assinatura do jar utilize o comando jarsigner conforme abaixo: jarsigner -verify -keystore applet_keystore.jks meujar-assinado.jar 28