Bom, durante algum tempo procurei algo que pudesse me ajudar e foi muito difícil encontrar qualquer coisa sobre tal assunto, existe muita informação mas nada agrupado, então eu tive que ir buscando trecho a trecho, entendendo cada coisa pra poder juntar tudo em um pacote só e conseguir alcançar o objetivo que eu tinha:
– Assinar um documento com Token A3, deixando-o totalmente válido.
Vamos lá.
Primeira coisa que devemos saber é o que é preciso para assinar digitalmente documentos e atender as regras da ICP-Brasil.
1 – Possuir um Token A3 certificado pela ICP-Brasil. (Certisign, Serasa, Serpro, ETC). Deve ser da ICP-Brasil.
2 – Carimbador de Tempo certificado pela ICP-Brasil. O carimbador de tempo irá fornecer a data e a hora. A assinatura não pode ser “stampada” (Carimbada) pelo mesmo relógio que assinou o documento. Caso isso ocorra ficará um aviso na validação informando que aquela assinatura foi realizada com a data e a hora do computador. Existem alguns serviços, pagos, que fornecem esse carimbo. O carimbo de tempo é utilizado para certificar o dia e a hora que o documento foi assinado e faz-se necessário caso deseje-se validar a assinatura por tempo maior que o estipulado pelo certificado Digital.
3 – Existem duas formas de se assinar um documento:
- Embutir a assinatura no documento
- Criar um arquivo de contra-prova e assinar esse arquivo (assinatura externa)
No nosso caso vamos utlizar a assinatura embutida no documento, também conhecida como ‘embedded’.
Assinatura de Documentos
Dentro da forma de assinar escolhida existem duas opções para assinarmos o documento:
- Validando o documento
- Certificando o documento
Validar o Documento
Quando você assina validando significa que você esta concordando com algo. Exemplo disso são documentos que precisam de mais de uma assinatura. Nesse caso, este tipo de assinatura não bloqueia o documento para novas edições.
Certificando o Documento
Quando você assina certificando significa que aquele documento não poderá ser alterado.
Sempre que o documento certificado é alterado a assinatura é invalidada. Qualquer alteração no documento irá invalidar a assinatura, então, caso seja necessário assinar o documento por mais de uma pessoa e a primeira assinatura foi a que certificou o documento, quando a segunda pessoa assinar a primeira assinatura será invalidada pois, ao assinar o documento é embutido nele um código de validação, assim como algumas informações do certificado e isso altera o hash do arquivo e assim gera a invalidação da assinatura.
Certo, mas foi assinado e o documento precisa ser valido novamente, o que devo fazer? Basicamente você precisa solicitar para a primeira pessoa que assinou o documento certificando que assine, novamente, o documento certificando-o e dessa forma o documento volta a ser válido.
Entendido como será feito iremos começar a parte de código realmente.
Alguns entendimentos devem ser levados em consideração:
- Para assinar um documento faz-se necessário criar um applet pois será necessário acessar a máquina do usuário
- O Applet deverá ser assinado por um certificado de assinatura de códigos
- O arquivo de manifesto do applet deverá informar o nível de permissão que ele poderá ter.
Feito isso vamos as bibilotecas necessárias. Em nosso caso iremos trabalhar com o IText para realizar a assinatura de PDF’s.
Libs
- bcpkix-jdk15on-1.50.jar
- bcprov-jdk15on-150.jar
- itextpdf-5.4.5.jar
No meu caso, eu ainda utilizo algumas bibliotecas para realizar algumas conversões. Exemplo, eu utilizo o velocity para gerar texto e utilzo o JTidy que lê esse “texto” e transforma em PDF. Entenda que quando digo que gero texto com o velocity significa que ele me gera um HTML com base nas informações passadas. Mais pra frente, em outra oportunidade eu irei explicar como trabalhar com velocity que e JTidy, que a meu ver é uma excelente opção para substituir o Jasper, principalmente se você precisa de um PDF estilizado. Fica para a próxima esse assunto.
Estrutura Básica para a assinatura de documentos
Primeiro você deve instalar as bibliotecas do seu Token A3. O procedimento para a instalação do token seria este:
Linux debian based
- Acesse o Terminal e em seguida entre como root através do comando: su
- Atualize a lista de pacotes através do comando: apt-get update
- Instale os seguintes pacotes pcscd libccid através do comando: apt-get install pcscd libccid
- Baixe o arquivo anexado abaixo e instale através do comando: dpkg -i safesignidentityclient_3.0.77-Ubuntu_amd64.deb
- Execute o aplicativo tokenadmin
Windows
- Instalar o programa SafeSign
- Execute o aplicativo SafeSign
Ao conectar o Token na USB você verá que o aplicativo irá reconhecer o token e esse será um gerenciador desse token. Caso você venha a bloquear o token é por este programa que você deverá utilizar sua chave PUK para alterar sua senha.
Com tudo pronto, vamos ao código. Primeiramente vamos mostrar o código do assinador, o responsável pela assinatura.
Bom, basicamente vou assinar um Array de bytes, caso deseje assinar um documento que esta na máquina fisicamente basta ler o arquivo, criar o array e assinar ok?
Vamos lá.
Nossa classe se chamará SignerLibrary e será responsável por criar toda a regra para assinatura.
public class SignerLibrary { private static final String PKCS11_KEYSTORE_TYPE = "PKCS11"; private static final String X509_CERTIFICATE_TYPE = "X.509"; private static final String CERTIFICATION_CHAIN_ENCODING = "PkiPath"; private static final String SUN_PKCS11_PROVIDER_CLASS = "sun.security.pkcs11.SunPKCS11"; private static SignerApplet applet; private KeyStore userKeyStore = null; private PrivateKeyAndCertChain privateKeyAndCertChain = null; private CertificationChainAndSignatureBase64 signingResult = new CertificationChainAndSignatureBase64(); private Certificate[] certChain = null; private PublicKey publicKey = null; private PrivateKey privateKey = null; private X509Certificate certificate = null; private String name; private Provider pkcs11Provider; private boolean mResult = false; private byte[] file; private byte[] cert;
Ela terá a aparência acima. Claro, ai são só os atributos da classe. Vamos aos métodos.
A assinatura do meu método será:
private byte[] signDocument(String aFileName, String certname, String aPinCode, String cpfAssinador, Boolean finalizarDocumento) throws DocumentSignException, DocumentException, IOException
Como dito, eu vou assinar um array de bytes, mas eu não estou recebendo esse array de bytes para ser assinado.
Bom, como eu manipulo esses bytes em mais de um lugar eu optei em criar o objeto no escopo da classe.
Mais a frente veremos o código completo.
Explicando o que é cada atributo:
String aFileName
Por receber um array de bytes eu solicito o nome do arquivo apenas para criar, temporariamente, o arquivo que será “escrito” realmente. No final o que ocorre é uma cópia de um arquivo para outro e este novo arquivo é embutido a assinatura.
String certname
O nome da biblioteca que tem acesso ao Token.
No linux essa biblioteca é: libaetpkss.so.3.0.2528
No Windows essa bibiloteca é: aetpksse.dll
String aPinCode
O pinCode do token, ou seja, a senha dele pois é com ela que irei pegar o keystore do token e deste keystore irei extrair a chave privada que será usada para assinar o documento.
String cpfAssinatura Boolean finalizarDocumento
Estes dois atributos foram criados devido a minha necessidade. Uma regra imposta é que, somente a pessoa que estivesse logada no nosso sistema era quem poderia assinar o documento e ela poderia optar em finalizar o documento ou apenas assiná-lo. Minha aplicação é web e por conta disso foi criado tais restrições de negócio. Caso não seja importante basta eliminar tal parte do código.
Para fazer o serviço temos alguns métodos de auxílio que serão exibidos no decorrer desse post.
O método então, por completo, ficaria assim:
private byte[] signDocument(String aFileName, String certname, String aPinCode, String cpfAssinador, Boolean finalizarDocumento) throws DocumentSignException, DocumentException, IOException { extractCertificateInformation(certname, aPinCode, cpfAssinador); PdfReader reader = null; OutputStream fow = null; PdfStamper stamper = null; FileInputStream fis = null; ByteArrayOutputStream bos = null; byte[] bytes = null; String filename = aFileName; try { filename = aFileName.substring(0,aFileName.length()-4); if(filename.contains("_signed")){ filename = filename.substring(0,filename.indexOf("_signed")); } ByteArrayOutputStream out = criarStampas(reader, cpfAssinador); filename += "_signed.pdf"; File fileToWrite = File.createTempFile(filename, null); reader = new PdfReader(out.toByteArray()); fow = new FileOutputStream(fileToWrite); stamper = PdfStamper.createSignature(reader, fow,'\0', null, true); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); appearance.setReason("Assinatura de documento"); appearance.setLocation("Meu Local"); appearance.setVisibleSignature(name); if(finalizarDocumento) { appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED); } else { appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); } appearance.setSignDate(Calendar.getInstance()); Authenticator.setDefault(new ProxyAuthenticator("usuario", "senha")); System.setProperty("http.proxyHost", "proxy"); System.setProperty("http.proxyPort", "porta"); String tsaUrl = "tsaUrl"; TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle(tsaUrl); tsaClient.setTSAInfo(new TSAInfoBouncyCastle() { @Override public void inspectTimeStampTokenInfo(TimeStampTokenInfo info) { System.out.println(info.getGenTime()); } } ); ExternalDigest digest = new BouncyCastleDigest(); OcspClient ocspClient = new OcspClientBouncyCastle(); ExternalSignature signature = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, pkcs11Provider.getName()); MakeSignature.signDetached(appearance, digest, signature, certChain, null, ocspClient, tsaClient, 0, CryptoStandard.CMS); fis = new FileInputStream(fileToWrite); bos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; for (int readNum; (readNum = fis.read(buf)) != -1;) { bos.write(buf, 0, readNum); } bytes = bos.toByteArray(); } catch (Throwable e) { e.printStackTrace(); } finally{ if(stamper != null) { stamper.close(); } if(fow != null) { fow.close(); } if(reader != null) { reader.close(); } if(bos != null) { bos.close(); } if(fis != null) { fis.close(); } } System.out.println("Documento Assinado com sucesso"); return bytes; }
Bom agora ta na hora de explicar né? Vamos lá. Já entenderam os parametros que são passados. Agora vamos explicar o que é o que nesse “bicho”.
A primeira coisa que eu faço é extrair os dados do certificado, passando o certName e o pinCode. (Método será colocado depois e explicado).
Depois eu crio a referência para os objetos que irei utilizar no método. Prestem atenção nos objetos da linha 3 e 5 (PdfReader / PdfStamper). Estes dois objetos serão os responsáveis por criar o documento que será assinado e as áreas de assinatura.
Feito isso, na linha 14 eu vou alterar o nome do objeto. Pra que isso? Como foi dito, o que criamos é uma cópia assinada do documento e para não sobreescrever o documento eu altero o nome dele, incluindo ao final do nome o sufixo ‘_signed’ que me informa que foi assinado.
Na linha 19 eu crio um ByteArrayOutputStream que é o retorno do método criarStampas. O método criar estampas faz o que ele se propõe a fazer, ou seja, ele vai criar áreas visuais no documento (conhecidas como estampas) que serão ajustadas de acordo com a quantidade de assinaturas, incluindo uma imagem padrão (caso deseje) e criará a área de assinatura, que será preenchida posteriormente. Vale lembrar que esse método ele irá criar um PdfReader com base no array de bytes que sua classe recebeu. Será explicado mais abaixo.
Na linha 22 é criado o arquivo temporário que irá receber a junção do arquivo com a assinatura.
Na linha 23 é criado o PdfReader com base no retorno do método de estampas.
na linha 26 é onde é criado a assinatura. O método createSignature recebe os seguintes parametros:
- PdfReader – O documento a ser assinado
- OutputStream – A stream do novo documento
- “\0″ – Char que informa se quer criar um novo documento ou se deseja manter o mesmo documento. No nosso casso o “\0″ mantém o mesmo documento
- Boolean – informando se existirá ou não adição no documento. Caso seja “true” significa que você poderá assinar documento mais de uma vez
Na linha 27 é criado o PdfSignatureAppearance com base na estampa (stamper). Fazemos isso para incluir algumas informações, visuais, na estampa do documento. Note as 3 linhas abaixo, vocês verão que foi informado a razão da assinatura, a localização (no nosso caso o nome da nossa empresa) e depois disso, na linha 30 é informado que a assinatura será visível no documento.
Note que na linha 30 é passado o parametro “name”. Esse parametro encontra-se no escopo da classe e esse parametro é utilizado para criar a estampa. Se você passar “joão” como ‘name’ e na linha 30 você informar “maria” ele não ira criar a estampa pois não existe estampa com o nome “maria” criada para o documento. No método criarStampas vocês verão que é criado o campo de assinatura com base no name passado. Esse name deve ser um valor unico, por exemplo, CPF do assinador.
As linhas 32 a 36 são para criar a nossa regra de assinatura. Ou seja, se eu vou assinar este documento apenas validando (sem certificação) ou se eu vou certificar o documento, como explicado no começo deste post.
As linhas 40 a 42 são para criar a regra de proxy. Caso sua aplicação esteja rodando atrás de um proxy será necessário ‘sair’ para poder pegar as informações do carimbador de tempo. No meu caso foi necessário informar o proxy. Caso não seja necessário basta comentar as linhas, não irá impactar em nenhum lugar.
Linha 43, Aqui o negócio começou a complicar um pouco. Vamos lá.
O que é TSA? TSA é a sigla para Timestamp Authority. Ou seja, autoridade carimbadora de tempo.
Essa linha você deverá informar a empresa que irá lhe prover o carimbador de tempo ou um servidor de hora. Caso seja preciso informar usuário e senha na criação do TSAClientBouncyCastle você poderá informar os dados. A criação do objeto ficaria assim:
TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle(tsaUrl, tsaLogin, tsaPassw);
Entenda, isso só se seu carimbador de tempo exigir usuário e senha. Caso contrário basta manter o padrão que foi explicado acima.
Entendido o que é isso, nas linhas 45 a 51 eu apenas exibo, no console do applet, a data que eu consegui recuperar do meu provedor de datas.
Linha 53, o Digest é o retorno produzido por alguma criptografação. Ele será utilizado para setar a assinatura.
Linha 54. OCSP. O que é isso? Online Certificate Status Protocol, ou seja, é a validação do teu certificado de forma online. Mas e se eu não tiver acesso a internet como isso seria produzido?
Bom, na internet existem ‘n’ exemplos de como embutir as listas de revogação daquele certificado dentro do documento assinado. Qual a vantagem?? não ter que realizar uma consulta online para saber se o certificado estava valido ou se estava em uma lista de revogação. Qual a desvantagem? O arquivo cresce de forma exponencial. Exemplo, um arquivo de 100kb pode chegar a 2Mb na primeira assinatura e isso cresce de acordo com a quantidade de assinaturas existentes pois, para cada assinatura, será embutido toda a lista de revogação.
Se for de interesse embutir as listas no arquivo existe uma forma de você saber qual sais as listas de revogação do certificado “espetado” no computador. Na internet também existem ‘n’ referências sobre isso.
Linha 55. Aqui nos iremos criar a assinatura. O método é muito simples, tudo é provido pelo IText. Você deverá informar qual a chave privada (que foi extraída na primeira linha do nosso método), o algorítimo que irá ser utilizado e o nome do provider de acesso.
Linha 56. Será criado, embutido, a assinatura no documento. Neste momento você vai dizer:
– qual a aparência da tua assinatura
– qual o retorno que será criado (digest)
– a assinatura
– a cadeia de confiança (ponto muito importante)
sua lista de CRL’s (Lista de certificados revogados), no nosso caso não iremos informar pois iremos informar o OCSP. Uma coisa anula outra, é possível informar os dois mas não faz sentido.
– o OCSP (criado na linha 54)
– o tsaClient (cliente do carimbador de tempo criado na linha 44)
– O tamanho da assinatura de queremos criar. Se for informado 0 será estimado no momento da criação. Se você informar um espaço (kb’s) menor do que o necessário para embutir a assinatura será lançado uma exceção.
– O tipo de assinatura. Signature.CMS ou Signature.CADES (não me preocupei em saber a diferença entre um e outro, se alguém tiver interesse só colocar aqui que a gente inclui no texto e coloca a referência)
Pronto. Feito isso o documento, do Reader que foi criado lá na linha 23, estará preenchido com a nova assinatura. Mas onde foi feito a junção do Reader com o FileTemp? Linha 26, na criação da assinatura lembra?
Agora, nas últimas linhas, basta escrever o arquivo novamente para o disco com o nome novo. No meu caso, como eu preciso devolver um array de bytes eu leio o FileTemp e escrevo os bytes. Caso não seja preciso basta adaptar a solução para escrever em disco diretamente.
Bom, agora vamos para os métodos “auxiliares”.
Começando:
public void extractCertificateInformation(String certname, String aPinCode, String cpfAssinador) throws DocumentSignException{ try { userKeyStore = loadKeyStoreFromSmartCard(certname, cert, aPinCode); } catch (Exception ex) { String errorMessage = "Erro ao ler repositório do smart card. \n" +"Possíveis erros: \n" +"- Smart Card não conectado. \n" +"- Biblioteca Inválida. \n" +"- Código PIN incorreto. \n"; throw new DocumentSignException(errorMessage, ex); } // Get the private key and its certification chain from the keystore try { privateKeyAndCertChain = getPrivateKeyAndCertChain(userKeyStore); } catch (GeneralSecurityException gsex) { String errorMessage = "Erro. Favor verificar senha"; throw new DocumentSignException(errorMessage, gsex); } // Check if the private key is available privateKey = privateKeyAndCertChain.mPrivateKey; if (privateKey == null) { String errorMessage = "Erro: chave privada do smart card."; throw new DocumentSignException(errorMessage); } // Check if public key is available publicKey = privateKeyAndCertChain.mPublicKey; if (publicKey == null) { String errorMessage = "Erro: chave pública do smart card."; throw new DocumentSignException(errorMessage); } // Check if X.509 certification chain is available certChain = privateKeyAndCertChain.mCertificationChain; if (certChain == null) { String errorMessage = "Erro: certificado do smart card."; throw new DocumentSignException(errorMessage); } // Create the result object // Save X.509 certification chain in the result encoded in Base64 try { signingResult.mCertificationChain = encodeX509CertChainToBase64(certChain); } catch (CertificateException cee) { String errorMessage = "Certificado inválido."; throw new DocumentSignException(errorMessage); } certificate = (X509Certificate) privateKeyAndCertChain.certificate; ExtratorUtil.parse(certificate, cpfAssinador); name = ExtratorUtil.pfDados; } private KeyStore loadKeyStoreFromSmartCard(String certName, byte[] cert, String aSmartCardPIN) throws GeneralSecurityException, IOException { // First configure the Sun PKCS#11 provider. It requires a stream (or file) // containing the configuration parameters - "name" and "library". File file = new File(certName); if(!file.exists()){ throw new IOException("Arquivo certificado não localizado"); } String pkcs11ConfigSettings = ""; pkcs11ConfigSettings = "name = SmartCard\n" + "library = " + file.getAbsolutePath(); byte[] pkcs11ConfigBytes = pkcs11ConfigSettings.getBytes(); ByteArrayInputStream confStream = new ByteArrayInputStream(pkcs11ConfigBytes); // Instantiate the provider dynamically with Java reflection try { Class<?> sunPkcs11Class = Class.forName(SUN_PKCS11_PROVIDER_CLASS); Constructor<?> pkcs11Constr = sunPkcs11Class.getConstructor( java.io.InputStream.class); pkcs11Provider = (Provider) pkcs11Constr.newInstance(confStream); Security.addProvider(pkcs11Provider); } catch (Exception e) { throw new KeyStoreException("Can initialize Sun PKCS#11 security " + "provider. Reason: " + e.getCause().getMessage()); } finally{ confStream.close(); } // Read the keystore form the smart card char[] pin = aSmartCardPIN.toCharArray(); KeyStore keyStore = KeyStore.getInstance(PKCS11_KEYSTORE_TYPE); keyStore.load(null, pin); return keyStore; } private PrivateKeyAndCertChain getPrivateKeyAndCertChain(KeyStore aKeyStore) throws GeneralSecurityException { Enumeration<?> aliasesEnum = aKeyStore.aliases(); if (aliasesEnum.hasMoreElements()) { String alias = (String)aliasesEnum.nextElement(); Certificate[] certificationChain = aKeyStore.getCertificateChain(alias); PrivateKey privateKey = (PrivateKey) aKeyStore.getKey(alias, null); PrivateKeyAndCertChain result = new PrivateKeyAndCertChain(); result.mPrivateKey = privateKey; result.mCertificationChain = certificationChain; result.certificate = aKeyStore.getCertificate(alias); result.mPublicKey = result.certificate.getPublicKey(); return result; } throw new KeyStoreException("The keystore is empty!"); } static class PrivateKeyAndCertChain { public PrivateKey mPrivateKey; public Certificate[] mCertificationChain; public Certificate certificate; public PublicKey mPublicKey; } /** * Data structure that holds a pair of Base64-encoded * certification chain and digital signature. */ static class CertificationChainAndSignatureBase64 { public String mCertificationChain = null; public String mSignature = null; } private String encodeX509CertChainToBase64(Certificate[] aCertificationChain) throws CertificateException { List<Certificate> certList = Arrays.asList(aCertificationChain); CertificateFactory certFactory = CertificateFactory.getInstance(X509_CERTIFICATE_TYPE); CertPath certPath = certFactory.generateCertPath(certList); byte[] certPathEncoded = certPath.getEncoded(CERTIFICATION_CHAIN_ENCODING); String base64encodedCertChain = Base64Utils.base64Encode(certPathEncoded); return base64encodedCertChain; }
Vamos lá, explicar novamente. Talvez essa seja a parte mais complicada de entender.
Linha 3: userKeyStore é um objeto do tipo KeyStore. Nessa linha existe a execução do método loadKeyStoreFromSmartCard.
Indo para esse método. O que ele faz?
Primeiro, na linha 65 é criado o objeto referente ao certificado, ou seja, referente a biblioteca que irá ler o smartcard.
Depois, na linha 71, é criado um arquivo de configuração do smartcard em tempo de execução. Esse arquivo pode estar fisicamente no disco e ser carregado com um recurso.
Com base nesse “arquivo” de configuração pegamos os bytes dele e criamos um Bytearray passando os bytes obtidos.
A partir dai o código caminha, apenas, para a adição do provider PKCS11.
Na linha 93 convertemos a String do pinCode em um array de char e passamos para o keystore, que foi carregado com base no tipo de Keystore que iremos utilizar (PKCS11). Ao término é carregado o keystore.
Pronto, voltamos ao método anterior e agora já temos nosso keystore totalmente pronto e funcional.
Vamos prosseguir.
Com o keystore em mãos vamos agora extrair a chave privada dele que será utilizada no método principal.
Na linha 100 temos o método getPrivateKeyAndCertChain, ele é responsável por pegar a chave privada e gerar a cadeia de confiança do nosso certificado.
A partir do Keystore passado temos nossos certificados que estão dentro do keystore. Esses certificado ele possui um nome, um Alias e do keystore conseguimos pegar todos os alias que existem dentro do próprio keystore, ou seja, conseguimos saber quantos certificados existem e recuperar todos eles pelo nome. No caso de um Token somente existirá um alias que será referente ao certificado que você adquiriu.
Na linha 104 extraímos todos os certificados que compões a cadeia de confiança do nosso certificado.
Na linha 105 pegamos a chave privada e na linha 110 nós setamos a chave pública. Essa chave pública é extraída do próprio certificado.
Eu criei uma classe estática que servirá de base para os outros métodos. Então, da linha 106 a 110 são setados, nessa classe estática, os valores que foram retirados do keystore.
Voltando para o método anterior, na linha 16, teremos o nosso objeto todo preenchido e segue-se então uma série de validações afim de garantir que tudo correrá de forma correta.
Na linha 49 nós realizamos a codificação do nosso certificado. Esse método irá incluir a cadeia de validação no resultado do certificado codificado.
O método em questão pega a lista de certificados da cadeia de confiança e a partir deles gera um lista de paths que serão codificados em um objeto Base64 e será retornado a String codificada desse objeto.
Feito isso e o certificado sendo valido, o código prossegue e chegamos as últimas linhas que tratam de uma validação que, logo no começo, foi dita. Aqui iremos extrair os dados do certificado em si e validar se o usuário que solicitou a assinatura é o mesmo usuário que esta logado em nossa aplicação.
Na linha 57 fazemos a chamada para a classe ExtratorUtil que tem como finalidade extrair algumas informações do certificado como, por exemplo, o CPF do usuário.
Abaixo segue o código da classe.
public class ExtratorUtil { public static final DERObjectIdentifier OID_PF_DADOS_TITULAR = new DERObjectIdentifier("2.16.76.1.3.1"); public static final DERObjectIdentifier OID_PJ_RESPONSAVEL = new DERObjectIdentifier("2.16.76.1.3.2"); public static final DERObjectIdentifier OID_PJ_DADOS_RESPONSAVEL = new DERObjectIdentifier("2.16.76.1.3.4"); public static final DERObjectIdentifier OID_CRL = new DERObjectIdentifier("2.5.29.31"); public static String pfDados; public static String getPFDados(X509Certificate cert, String cpfAssinador) throws DocumentSignException{ ExtratorUtil.parse(cert, cpfAssinador); return pfDados; } @SuppressWarnings("unused") public static void parse(X509Certificate cert, String cpfAssinador) throws DocumentSignException { try { Collection<?> col = X509ExtensionUtil.getSubjectAlternativeNames(cert); for (Object obj : col) { if (obj instanceof ArrayList) { ArrayList<?> lst = (ArrayList<?>) obj; Object value = lst.get(1); if (value instanceof DLSequence) { /** * DER Sequence * ObjectIdentifier * Tagged * DER Octet String */ DLSequence seq = (DLSequence) value; DERObjectIdentifier oid = (DERObjectIdentifier) seq.getObjectAt(0); DERTaggedObject tagged = (DERTaggedObject) seq.getObjectAt(1); String info = null; ASN1Primitive derObj = tagged.getObject(); if (derObj instanceof DEROctetString) { DEROctetString octet = (DEROctetString) derObj; info = new String(octet.getOctets()); } else if (derObj instanceof DERPrintableString) { DERPrintableString octet = (DERPrintableString) derObj; info = new String(octet.getOctets()); } else if (derObj instanceof DERUTF8String) { DERUTF8String str = (DERUTF8String) derObj; info = str.getString(); } if (oid.equals(OID_PF_DADOS_TITULAR) || oid.equals(OID_PJ_DADOS_RESPONSAVEL)) { String nascimento = info.substring(0, 8); String cpf = info.substring(8, 19); if(!cpf.equals(cpfAssinador)){ throw new DocumentSignException("CPF do Assinante diferente do CPF do usuário logado no sistema."); } String nis = info.substring(19, 30); String rg = info.substring(30, 45); if (!rg.equals("000000000000000")) { String ufExp = info.substring(45, 50); } pfDados = cpf; } if (oid.equals(OID_CRL)) { System.out.println(info); } } else { System.out.println("Valor desconhecido: " + value); } } } } catch (Exception e) { throw new DocumentSignException(e.getMessage()); } } }
Não irei me ater a explicar o que essa classe faz. Apenas entendam que, para cada registro do certificado existe um código específico e este código pode ser mapeado, basta incluir um novo DERObjectIdentifier e mapear o identificador.
Bom, retomando. Não sei se esta ficando didático mas no final irei colocar o código inteiro da classe e irei colocar os arquivos para download.
Vamos lá.
Ao realizar todo esse trabalho o retorno é todo colocado na nossa classe estática e podemos seguir com o fluxo normal.
Faltou uma última coisa e muito importante. Nosso arquivo de Manifesto. Vamos lá
Manifest-Version: 1.0 Codebase: * Permissions: all-permissions Application-Library-Allowable-Codebase: * Caller-Allowable-Codebase: * Application-Name: Digital Signer Class-Path: bcprov-jdk15on-150.jar bcpkix-jdk15on-1.50.jar itextpdf-5.4.5.jar
Aqui, o que realmente importa é a linha 3: Permissions, que se tornou obrigatório com o Java7 e claro, informar o classpath do teu applet.
Não sei se esqueci de alguma coisa. Mas nos arquivos tem as duas classes que utilizo para poder realizar tudo corretamente.
Lembrem-se, pra que isso tudo possa funcionar é necessário que isto tudo rode dentro de um applet e que o mesmo seja assinado. O código do applet fica por conta de vocês.
Qualquer dúvida manda ai nos comentários que eu tento agilizar a resposta e se ficou faltando algo, me desculpem, na hora que vc’s perceberem algo me avisa que eu corrijo.
Abraços!!!
Abaixo tem o link pro assinador.
Olá. Parabéns pelo post, muito informativo. Não testei o código ainda, mas parece muito promissor: eu também passei por muitos problemas tentando realizar a assinatura de documentos utilizando Java e o e-CPF/e-CNPJ.
Tenho algumas dúvidas:
1 – No início foi dito que o código é para realizar a assinatura utilizando um Token A3. O código suporta smartcards também? Se não, o que precisa mudar para aceitar smartcards?
2 – Eu utilizo Linux para desenvolver, boa parte dos meus usuários utilizam Windows, mas existe uma parcela considerável que utiliza Mac. Qual a configuração necessária para esses usuários para que eles possam assinar documentos com essa lib?
3 – Você tem esse código no GitHub ou outro repositório? Qual a licença de uso dele? O link pro assinador no final do post não está aparecendo.
Obrigado.
1 – Sim, funciona para smartcard sim. Creio que vc terá que adequar a sua realidade, claro, mas funciona sim. A idéia é a mesma, acessa a leitora via USB e coleta os dados do keystore.
2 – No windows vc precisa instalar um programa chamado safesign da Certisign. Dessa forma ele instala os drives para acessar o leitor. Com relação ao Mac creio que vc precise instalar os drives do PKCS11, mas ai não lembro de cabeça como fazer, na internet deve ter como fazer visto que MAC é unix então segue o mesmo padrão linux ok?
3 – O link na página realmente não ta funcionando. Mas no topo do site tem um menu “Arquivos” que dentro dele ta o Signer.zip ok? É o mesmo arquivo.
Olá legal o post.
Mas cade o link para baixar o assinador ? está desligado.
Poderia me fornecer o link ?
No menu existe um chamado “Arquivos”, Lá tem o assinador.
Marcus Mazzo Laprano, muito bom seu tutorial simplesmente é o melhor que encontrei até agora….
estou precisando implementar esta assinatura no meu sistema abaixo segue as perguntas:
1) como faço para criar o applet e executar o código pelo ou com o applet?
2) por acaso você teria algum tutorial de applet?
desde já agradeço.
Bom dia Marcus, estou usando suas classes e a assinatura as vezes funciona as vezes não. no console do Java aparece “Documento Assinado com sucesso” mas as vezes não sai nada no PDF. As vezes preciso reiniciar a máquina para que a assinatura funcione novamente. E nunca deu certo de assinar mais de um documento seguido. Você sabe porque isso acontece?
A assinatura normalmente não é visível. Nós colocamos uma estampa apenas para informar que foi assinado. É preciso que você debug o código para identificar por qual motivo não esta assinando.
Você precisa visualizar o arquivo em um programa que seja capaz de validar assinaturas (adobe por exemplo). Nele irá aparecer uma verificação, independente de existir ou não a estampa.
Se você precisa reiniciar a maquina é pq existe algum problema no visualizador do arquivo. Novamente, a estampa não significa nada, é apenas visual. A assinatura em si é um trecho de código embutido no arquivo PDF que contém informações.
É possível que esteja ficando travado o retorno do applet e por isso você esta tendo problema ao assinar o segundo documento. Só pela descrição não é possível verificar o problema. Se for possível me envia o código por email que eu dou uma olhada.