Jose Sanches Posted June 5, 2021 at 06:28 PM Report Share #622560 Posted June 5, 2021 at 06:28 PM (edited) Olá Viva, Ando com um problema já algum tempo. Necessito de assinar um PDF, recorrendo aos serviços da AMA, com Chave Móvel Digital. Recentemente vi um tópico em C# aqui. Tendo por base os contributos do @Labreu, tentei implementar o projeto em java (está disponível uma versão no github), não correu bem. Enquadramento: Obtém certificados utilizador Cria assinatura vazia no PDF Obtém hash do PDF a assinar, com prefixo da AMA Cria PDF temporário com a assinatura vazia Envia hash para AMA AMA -> envio SMS para envio de hash assinada Obtenção de hash assinada e inclusão da mesma e PDF. Criação do PDF assinado, mas falha. Alguma ideia/sugestão? Obrigado Edited June 6, 2021 at 08:17 PM by Jose Sanches Link to comment Share on other sites More sharing options...
marcolopes Posted June 29, 2021 at 05:00 PM Report Share #622813 Posted June 29, 2021 at 05:00 PM Porque não implementar antes o sistema SAFE? https://www.autenticacao.gov.pt/serviço-de-assinatura-de-faturas-eletrónicas-safe- The simplest explanation is usually the correct one JAVA Utilities: https://github.com/marcolopes/dma Link to comment Share on other sites More sharing options...
americob Posted June 29, 2021 at 06:03 PM Report Share #622814 Posted June 29, 2021 at 06:03 PM 53 minutos atrás, marcolopes disse: Porque não implementar antes o sistema SAFE? https://www.autenticacao.gov.pt/serviço-de-assinatura-de-faturas-eletrónicas-safe- O SAFE é para assinar Faturas Eletronicas. Neste caso parece-me ser para assinar qualquer PDF. Link to comment Share on other sites More sharing options...
marcolopes Posted June 29, 2021 at 07:42 PM Report Share #622815 Posted June 29, 2021 at 07:42 PM 1 hour ago, americob said: O SAFE é para assinar Faturas Eletronicas. Neste caso parece-me ser para assinar qualquer PDF. O SAFE assina PDF... o PDF pode ser uma factura electrónica, como poder ser um outro documento qualquer 😄 The simplest explanation is usually the correct one JAVA Utilities: https://github.com/marcolopes/dma Link to comment Share on other sites More sharing options...
americob Posted June 30, 2021 at 09:57 AM Report Share #622819 Posted June 30, 2021 at 09:57 AM 14 horas atrás, marcolopes disse: O SAFE assina PDF... o PDF pode ser uma factura electrónica, como poder ser um outro documento qualquer 😄 Sim, em princípio, o SAFE pode assinar um PDF qualquer. Mas, não ficará com a assinatura: "em representação da Sociedade "xxx" com poderes para assinar faturas"? Parece-me que é assim que funciona, já que para poder assinar Faturas é necessário pedir a representação da Sociedade respetiva, com poderes para diversos atos, neste caso a assinatura de Faturas. É mesmo preciso enviar a Certidão Permanente da empresa para poderem confirmar que o assinante tem realmente poderes para o fazer, e só depois eles libertam essa opção no Cartão do Cidadão do assinante. No meu caso, uso o Cartão do Cidadão para assinar Documentos como Contabilista Certificado e é assim que aparece a assinatura nos PDFs, quando opto pela respetiva qualidade. 1 Report Link to comment Share on other sites More sharing options...
marcolopes Posted June 30, 2021 at 03:45 PM Report Share #622826 Posted June 30, 2021 at 03:45 PM 5 hours ago, americob said: Sim, em princípio, o SAFE pode assinar um PDF qualquer. Mas, não ficará com a assinatura: "em representação da Sociedade "xxx" com poderes para assinar faturas"? Parece-me que é assim que funciona, já que para poder assinar Faturas é necessário pedir a representação da Sociedade respetiva, com poderes para diversos atos, neste caso a assinatura de Faturas. É mesmo preciso enviar a Certidão Permanente da empresa para poderem confirmar que o assinante tem realmente poderes para o fazer, e só depois eles libertam essa opção no Cartão do Cidadão do assinante. No meu caso, uso o Cartão do Cidadão para assinar Documentos como Contabilista Certificado e é assim que aparece a assinatura nos PDFs, quando opto pela respetiva qualidade. Nesse caso, e se o objectivo é mesmo assinar um qualquer PDF documental, o SAFE não será o mais indicado não senhor... Acho que devemos criar um tópico para implementação SAFE... The simplest explanation is usually the correct one JAVA Utilities: https://github.com/marcolopes/dma Link to comment Share on other sites More sharing options...
Jose Sanches Posted June 30, 2021 at 03:50 PM Author Report Share #622827 Posted June 30, 2021 at 03:50 PM (edited) Olá, A ideia a criar uma uma funcionalidade e integra-la numa aplicação que possa assinar PDF's. Existem algumas abordagens com as frameworks iText v7 ou PDFBox v2, mas nenhuma foi satisfatória. já ultrapassei alguns problemas... mas ainda "não está lá", dento de dias vou disponibilizar uma nova versão com os progressos. Edited June 30, 2021 at 03:52 PM by Jose Sanches Link to comment Share on other sites More sharing options...
marcolopes Posted June 30, 2021 at 04:03 PM Report Share #622830 Posted June 30, 2021 at 04:03 PM Criei um tópico próprio para o SAFE: https://www.portugal-a-programar.pt/forums/topic/79044-serviço-de-assinatura-de-faturas-eletrónicas-safe/ Acho que vai ser o caminho de muita gente para permitir assinaturas de FE sem limite, pela módica quantia de 20 euros por ano... The simplest explanation is usually the correct one JAVA Utilities: https://github.com/marcolopes/dma Link to comment Share on other sites More sharing options...
Jose Sanches Posted June 30, 2021 at 04:07 PM Author Report Share #622831 Posted June 30, 2021 at 04:07 PM É uma abordagem válida como outra qualquer. Vou continuar nesta abordagem... Link to comment Share on other sites More sharing options...
marcolopes Posted June 30, 2021 at 04:12 PM Report Share #622832 Posted June 30, 2021 at 04:12 PM (edited) 6 hours ago, Jose Sanches said: É uma abordagem válida como outra qualquer. Vou continuar nesta abordagem... Esta abordagem acaba por ser melhor, pois permite assinar qualquer tipo de documento A implementação permite uma automatização completa sem interação do utilizador? EDIT: Agora fiquei COMPLETAMENTE baralhado (deve ser da hora...) Então se a assinatura com CHAVE MÓVEL ou directamente pelo CC permite assinar FACTURAS, porque razão foi criado o sistema SAFE??? https://www.autenticacao.gov.pt/web/guest/atributos-profissionais/assinatura-digital Pelo que posso apurar será para evitar uma "autenticação" a cada assinatura... facilitando a assinatura de vários documentos... Edited June 30, 2021 at 10:53 PM by marcolopes The simplest explanation is usually the correct one JAVA Utilities: https://github.com/marcolopes/dma Link to comment Share on other sites More sharing options...
americob Posted July 2, 2021 at 09:18 AM Report Share #622853 Posted July 2, 2021 at 09:18 AM Em 30/06/2021 às 17:12, marcolopes disse: Esta abordagem acaba por ser melhor, pois permite assinar qualquer tipo de documento A implementação permite uma automatização completa sem interação do utilizador? EDIT: Agora fiquei COMPLETAMENTE baralhado (deve ser da hora...) Então se a assinatura com CHAVE MÓVEL ou directamente pelo CC permite assinar FACTURAS, porque razão foi criado o sistema SAFE??? https://www.autenticacao.gov.pt/web/guest/atributos-profissionais/assinatura-digital Pelo que posso apurar será para evitar uma "autenticação" a cada assinatura... facilitando a assinatura de vários documentos... Sim, a ideia com que fiquei é essa mesmo. Se houver integração com o nosso software, não precisa código da chave móvel para cada assinatura, só no login do nosso programa. Ou seja, o gerente abre o programa de manhã e pode deixar um funcionário a fazer faturas o dia todo. Não fiquei com certezas sobre a durabilidade do login perante o sistema SAFE (caduca ao fim de x minutos sem utilização???) 1 Report Link to comment Share on other sites More sharing options...
bioshock Posted July 2, 2021 at 04:55 PM Report Share #622856 Posted July 2, 2021 at 04:55 PM 7 hours ago, americob said: Não fiquei com certezas sobre a durabilidade do login perante o sistema SAFE (caduca ao fim de x minutos sem utilização???) Se fizeres a integração via oAuth, sim expira mas podes renovar o token de acesso sem intervenção do utilizador. Não andava muito atento a estas assinaturas electrónicas e tinha/tenho ideia que a sua obrigatoriedade é só para empresas públicas (?), para além disso vi os preços de algumas entidades e é impracticável. Após ter conhecimento do SAFE e do custo (2 primeiros anos grátis e depois 40eur a cada 2 anos) parece-me uma solução apetecível para introduzir no meu software, independentemente de ser apenas ou não uma obrigatoriedade para a função pública. 2 Report Link to comment Share on other sites More sharing options...
Jose Sanches Posted July 8, 2021 at 06:16 PM Author Report Share #622904 Posted July 8, 2021 at 06:16 PM Olá Boa tarde, Deixo aqui uma abordagem que assina o PDF sem a mensagem o documento foi alterado depois de assinando. O PDF é assinado com a invocação dos serviços web da AMA. private static final String input = "c:/tmp/ama/PDF-1.pdf"; private static final String output = "c:/tmp/ama/signed_signed.pdf"; public static void main(String[] args) throws IOException, GeneralSecurityException { Security.addProvider(new BouncyCastleProvider()); PdfReader reader = new PdfReader(input); OutputStream fos = new FileOutputStream(output); StampingProperties stampingProperties = new StampingProperties(); //For any signature in the Pdf but the first one, you need to use appendMode // stampingProperties.useAppendMode(); PdfSigner pdfSigner = new PdfSigner(reader, fos, stampingProperties); /*you can modify the signature appearance */ PdfSignatureAppearance appearance = pdfSigner.getSignatureAppearance(); appearance.setPageRect(new Rectangle(36, 508, 254, 200)); appearance.setPageNumber(1); appearance.setLayer2FontSize(10f); appearance.setReason("Teste Assinatura"); appearance.setLocation("Lisboa"); IExternalSignatureContainer gsContainer = new GSSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached); pdfSigner.signExternalContainer(gsContainer, 8049); } public class GSSignatureContainer implements IExternalSignatureContainer { /* Signature dictionary. Filter and SubFilter. */ private PdfDictionary sigDic; public GSSignatureContainer( PdfName filter, PdfName subFilter) { sigDic = new PdfDictionary(); sigDic.put(PdfName.Filter, filter); sigDic.put(PdfName.SubFilter, subFilter); } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { try { //get all certificates (3) from client via web service Certificate[] chain = new CallAMA().getCertificates(); String hashAlgorithm = DigestAlgorithms.SHA256;//"SHA-256"; BouncyCastleDigest digest = new BouncyCastleDigest(); MessageDigest md = digest.getMessageDigest(hashAlgorithm); byte[] hash = DigestAlgorithms.digest(data, md); PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false); //Collection<byte[]> ocsp = new OcspClientBouncyCastle(null); OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null); Collection<byte[]> ocsp = new ArrayList<byte[]>(); for(var i = 0; i < chain.length - 1; i++) { byte[] encoded = ocspClient.getEncoded((X509Certificate)chain[i], (X509Certificate)chain[i + 1], null); if(encoded != null) ocsp.add(encoded); } byte[] attributeBytes = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CADES, ocsp, null); //criar sha256 message digest byte[] attributeBytesDigest = MessageDigest.getInstance(hashAlgorithm).digest(attributeBytes); /**************************************************** * CALL client (AMA) -> receive SMS and signed hash * ****************************************************/ byte[] signedHash = new CallAMA().getHashSignedByAma(attributeBytesDigest); sgn.setExternalDigest(signedHash, null, "RSA"); ITSAClient tsaClient = null;//new GSTSAClient(access); return sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CADES, tsaClient, ocsp, null); } catch (IOException | GeneralSecurityException de) { de.printStackTrace(); throw new GeneralSecurityException(de); } } @Override public void modifySigningDictionary(PdfDictionary signDic) { signDic.putAll(sigDic); } } } 1 Report Link to comment Share on other sites More sharing options...
Gus Posted July 23, 2021 at 07:26 AM Report Share #622954 Posted July 23, 2021 at 07:26 AM (edited) Olá José, Conseguiste chegar a uma versão que funcione e resulte no PDF assinado? Estou também a tentar assinar PDFs usando a CMD com os serviços da AMA e não consigo obter um ficheiro válido. Edited July 23, 2021 at 08:51 AM by Gus Link to comment Share on other sites More sharing options...
Jose Sanches Posted July 23, 2021 at 03:26 PM Author Report Share #622958 Posted July 23, 2021 at 03:26 PM Ola Gus, Esta ação não é fácil. A falta de documentação técnica assim como a falta de exemplos torna tudo mais complicado. No exemplo que disponibilizei (o mais recente), assina o PDF e o Adobe Reader dá a mensagem "A validade da assinatura é desconhecida" (rectângulo laranja). Questionei a AMA com o exemplo, e a informação que obtive foi: "No ambiente de pré-produção é natural que não seja verde - isso apenas em produção." Mas.... não sei 1 Report Link to comment Share on other sites More sharing options...
Gus Posted July 26, 2021 at 09:53 AM Report Share #622961 Posted July 26, 2021 at 09:53 AM On 7/23/2021 at 4:26 PM, Jose Sanches said: "No ambiente de pré-produção é natural que não seja verde - isso apenas em produção." Sim, isso é verdade, só em produção vai ser possível confirmar que está tudo a funcionar. O meu problema parece que está a ser ao aplicar o hash devolvido pela AMA, o PDF final fica sempre com o erro de que foi modificado ou corrompido desde que foi colocado o placeholder da assinatura. Tinha visto aqui que também estavas com alguns problemas, mas parece que conseguiste que funcionasse: https://stackoverflow.com/questions/67889692/java-itext7-deferred-with-sign-prefix-producing-invalid-signed-pdf É possível partilhares o código da versão que achas que funciona? Ou este último que colocaste neste tópico funciona? Link to comment Share on other sites More sharing options...
Jose Sanches Posted July 27, 2021 at 06:03 PM Author Report Share #622968 Posted July 27, 2021 at 06:03 PM (edited) Olá Gus, O meu último post no stackoverflow já resolve o problema que referes. Voltei a questionar a AMA com o exemplo no sentido de aferir se estava tudo bem. Passei o PDF pelo código que me valida a assinatura (mas ainda em triângulo laranja) Estou a ultimar uma versão com a inclusão de um PDF temporário (para incluir a assinatura vazia e obter o HASH), depois é invocar o endpoint da AMA (PRE + HASH) obter o HASH assinado e inclui-lo no PDF (criar uma novo ficheiro) assinado. Nota: se tudo estiver bem... actualizo o código no github com uma versão web funcional Edited July 27, 2021 at 06:11 PM by Jose Sanches 1 Report Link to comment Share on other sites More sharing options...
Jose Sanches Posted July 31, 2021 at 08:22 PM Author Report Share #622992 Posted July 31, 2021 at 08:22 PM Olá, Finalmente resolvo o problema... Nota: O PDF assinado não apresenta problemas (foi verificado com código para validar a assinatura). O PDF apresente um triângulo laranja (natural de pré-produção) - Falha na AMA a URL: http://ocsp.cmd.teste.cartaodecidadao.pt/publico/ocsp para validação OCSP private static final String input = "c:/tmp/ama/PDF-1.pdf"; private static final String tmp = "c:/tmp/ama/tmpPDF.pdf"; private static final String output = "c:/tmp/ama/signed.pdf"; public static void main(String[] args) throws IOException, GeneralSecurityException { BouncyCastleProvider providerBC = new BouncyCastleProvider(); Security.addProvider(providerBC); //Obter via serviço web os certificado (a AMA devolve 3) Certificate[] chain = new CallAMA().getCertificates(); Teste app = new Teste(); //A partir do PDF original, lê o HASH para ser assinado, //Cria um PDF temporário (com uma assinatura vazia). //Nota: "sig" é o nome do campo que identifica a assinatura no PDF byte[] hash4Sign = app.emptySignature(input, tmp, "sig", chain); //Concatena "sha256SigPrefix" com o HASH obtido do PDF (o HASH deve ter 32 bytes, com o valor "sha256SigPrefix" fica com 51 bytes) //Envia o HASH (51 bytes) para a AMA //Aguarda o SMS com o codigo e envia-o novamente para a AMA via serviço Web //Por fim, recebe o HASH (da AMA) assinado //Nota: Importante, aqui deve ser implementada toda a logica da aplicação web, não o faço, esta classe é um exemplo byte[] signedHash = new CallAMA().getHashSignedByAma(hash4Sign); //insere o HASH (da AMA) no PDF temporário e cria um ficheiro final assinado app.createSignature(signedHash, tmp, output, "sig", chain); } public byte[] emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, GeneralSecurityException { PdfReader reader = new PdfReader(src); PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties().useAppendMode()); PdfSignatureAppearance appearance = signer.getSignatureAppearance(); appearance .setLayer2FontSize(6) .setPageRect(new Rectangle(36, 748, 250, 50)) .setPageNumber(1) .setCertificate(chain[0]) .setSignatureCreator("ORIGEM"); signer.setFieldName(fieldname); /* ExternalBlankSignatureContainer constructor will create the PdfDictionary for the signature * information and will insert the /Filter and /SubFilter values into this dictionary. * It will leave just a blank placeholder for the signature that is to be inserted later. */ MyExternalBlankSignatureContainer external = new MyExternalBlankSignatureContainer(chain, PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached); // Sign the document using an external container. // 8192 is the size of the empty signature placeholder. signer.signExternalContainer(external, 8192); byte[] hash4Sign = external.getHash4Sign(); return hash4Sign; } public void createSignature(byte[] hashSigned, String src, String dest, String fieldName, Certificate[] chain) throws IOException, GeneralSecurityException { PdfReader reader = new PdfReader(src); try(FileOutputStream os = new FileOutputStream(dest)) { PdfSigner signer = new PdfSigner(reader, os, new StampingProperties()); IExternalSignatureContainer external = new MyExternalSignatureContainer(hashSigned, chain, PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached); // Signs a PDF where space was already reserved. The field must cover the whole document. signer.signDeferred(signer.getDocument(), fieldName, os, external); } } class MyExternalSignatureContainer implements IExternalSignatureContainer { /* Signature dictionary. Filter and SubFilter. */ private PdfDictionary sigDic; private byte[] signedHash =null; private Certificate[] chain = null; public MyExternalSignatureContainer(byte[] _signedHash, Certificate[] _chain, PdfName filter, PdfName subFilter) { sigDic = new PdfDictionary(); sigDic.put(PdfName.Filter, filter); sigDic.put(PdfName.SubFilter, subFilter); signedHash = _signedHash; chain = _chain; } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { try { String hashAlgorithm = DigestAlgorithms.SHA256;//"SHA-256"; BouncyCastleDigest digest = new BouncyCastleDigest(); MessageDigest md = digest.getMessageDigest(hashAlgorithm); byte[] hash = DigestAlgorithms.digest(data, md); PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false); OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null); Collection<byte[]> ocsp = new ArrayList<byte[]>(); for(var i = 0; i < chain.length - 1; i++) { byte[] encoded = ocspClient.getEncoded((X509Certificate)chain[i], (X509Certificate)chain[i + 1], null); if(encoded != null) ocsp.add(encoded); } sgn.setExternalDigest(signedHash, null, "RSA"); ITSAClient tsaClient = null;//new GSTSAClient(access); return sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CADES, tsaClient, ocsp, null); } catch (IOException | GeneralSecurityException de) { de.printStackTrace(); throw new GeneralSecurityException(de); } } @Override public void modifySigningDictionary(PdfDictionary signDic) { signDic.putAll(sigDic); } } class MyExternalBlankSignatureContainer implements IExternalSignatureContainer { /* Signature dictionary. Filter and SubFilter. */ private PdfDictionary sigDic; private byte[] hash4Sign = null; private Certificate[] chain = null; public MyExternalBlankSignatureContainer(Certificate[] _chain, PdfName filter, PdfName subFilter) { sigDic = new PdfDictionary(); sigDic.put(PdfName.Filter, filter); sigDic.put(PdfName.SubFilter, subFilter); chain = _chain; } public byte[] getHash4Sign() { return hash4Sign; } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { try { String hashAlgorithm = DigestAlgorithms.SHA256;//"SHA-256"; BouncyCastleDigest digest = new BouncyCastleDigest(); MessageDigest md = digest.getMessageDigest(hashAlgorithm); byte[] hash = DigestAlgorithms.digest(data, md); PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false); OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null); Collection<byte[]> ocsp = new ArrayList<byte[]>(); for(var i = 0; i < chain.length - 1; i++) { byte[] encoded = ocspClient.getEncoded((X509Certificate)chain[i], (X509Certificate)chain[i + 1], null); if(encoded != null) ocsp.add(encoded); } byte[] attributeBytes = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CADES, ocsp, null); //create sha256 message digest hash4Sign = MessageDigest.getInstance(hashAlgorithm).digest(attributeBytes); return new byte[0]; } catch (IOException | GeneralSecurityException de) { de.printStackTrace(); throw new GeneralSecurityException(de); } } @Override public void modifySigningDictionary(PdfDictionary signDic) { signDic.putAll(sigDic); } } 1 Report Link to comment Share on other sites More sharing options...
Gus Posted August 2, 2021 at 11:53 AM Report Share #622999 Posted August 2, 2021 at 11:53 AM Olá José, Muito obrigado, já consegui meter a funcionar do meu lado! Não estava a aplicar bem o hash devolvido pelo webservice e o PDF ficava com a assinatura corrompida...a documentação que a AMA dá (quase nenhuma) é péssima. 1 Report Link to comment Share on other sites More sharing options...
Popular Post Jose Sanches Posted August 26, 2021 at 12:57 PM Author Popular Post Report Share #623460 Posted August 26, 2021 at 12:57 PM (edited) Olá boa tarde, Disponibilizei uma aplicação funcional no GitHub. Nota: esta aplicação deverá ser "vista" como ponto de partida. Edited August 26, 2021 at 12:58 PM by Jose Sanches 6 1 Report Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now