Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exemplo em java de como assinar #7

Open
leandro47 opened this issue Dec 3, 2021 · 25 comments
Open

Exemplo em java de como assinar #7

leandro47 opened this issue Dec 3, 2021 · 25 comments
Assignees

Comments

@leandro47
Copy link

Alguem tem algum exemplo utilizando o iText para assinar o documento?
image

@flaviossantana
Copy link

flaviossantana commented Dec 14, 2021

Também estou a procura de como realizar essa assinatura em "envelopada" no Java.

Cheguei nesse exemplo aqui mais ainda não entendi como utilizar o certificado retornado do gov.br, gerar o hash com o calculo do hash:
https://stackoverflow.com/questions/50803132/retrieve-the-pkcs7-file-from-pdf-and-co-sign

@caduvieira
Copy link
Contributor

@caduvieira
Copy link
Contributor

@gpieri teria outros exemplos?

@gpieri
Copy link
Collaborator

gpieri commented Dec 14, 2021

@caduvieira Temos esse pseudo-exemplo em C# ilustrando o uso da API do IText.

Não sei se o exemplo está compilando. Nunca o compilei, foi usado de forma ilustrativa.

De toda forma, é exatamente o mesmo processo usado para assinaturas ICP-Brasil no contexto do DOC-ICP-17.

using com.itextpdf.text.pdf.security;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace Assinador
{
    class Novo
    {

        public static void Main()
        {
            EmbedSignature(@"C:\temp\exemplotemporario.pdf", @"C:\temp\exemploassinado.pdf", "test");
        }

        public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName)
        {
            using (PdfReader reader = new PdfReader(tempPdf))
            {
                PdfSigner signer = new PdfSigner(reader, new FileStream(signedPdf, FileMode.Create), new StampingProperties());

                // Create the signature appearance
                Rectangle rect = new Rectangle(36, 648, 200, 100);
                PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
                appearance
                    .SetReason(reason)
                    .SetLocation(location)
                    .SetPageRect(rect)
                    .SetPageNumber(1);
                signer.SetFieldName("sig");

                IExternalSignature pks = new ServerSignature();

                IExternalSignatureContainer externalSignatureContainer = new GovBrSignatureContainer(certBytes, pk7);
                signer.signExternalContainer(externalSignatureContainer, 8192);
            }
        }

       

        public class GovBrSignatureContainer : IExternalSignatureContainer
        {
            public GovBrSignatureContainer()
            {
            }

            public byte[] Sign(Stream data)
            {
                try
                {
                    Org.BouncyCastle.Crypto.Digests.Sha256Digest myHash = new Org.BouncyCastle.Crypto.Digests.Sha256Digest();
                    myHash.BlockUpdate(data, 0, data.Length);
                    byte[] compArr = new byte[myHash.GetDigestSize()];
                    myHash.DoFinal(compArr, 0);
                    var sha256hash = System.Convert.ToBase64String(compArr);

                    Uri url = new Uri("https://assinatura-api.staging.iti.br/externo/v2/assinarPKCS7");
                    var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                    httpWebRequest.Method = "POST";

                    using (Stream stream = httpWebRequest.GetRequestStream())
                    {
                        stream.Write("{\"hashBase64\": " + sha256hash +  "}", 0, message.Length);
                    }

                    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        Stream stream = httpResponse.GetResponseStream();
                        stream.CopyTo(memoryStream);
                        stream.Close();

                        return memoryStream.ToArray();
                    }
                }
                catch (IOException e)
                {
                    throw new PdfException(e);
                }

            }

            public void ModifySigningDictionary(PdfDictionary signDic)
            {
                signDic.Put(PdfName.FILTER, PdfName.Adobe_PPKLite);
                signDic.Put(PdfName.SUBFILTER, PdfName.Adbe_pkcs7_detached);
            }
        }
    }
}

@caduvieira caduvieira removed their assignment Dec 15, 2021
@caduvieira
Copy link
Contributor

Não faço parte mais da SGD. Devolvendo para você. Obrigado pela resposta.

@gpieri
Copy link
Collaborator

gpieri commented Dec 15, 2021

ok, @caduvieira. Obrigado!

@flaviossantana
Copy link

Alguem tem algum exemplo utilizando o iText para assinar o documento? image

Conseguiu avançar na assinatura com java?
Estou voltando hoje para essa integração.

@leandro47
Copy link
Author

leandro47 commented Jan 18, 2022

sim, depois que pega o certificado e a assinatura funcionou com esses metodo, mas lembrando que antes de mandar o arquivo para o gov.br é necessario prepara-lo antes (alocando o espaço da assinatura) caso contrario a assinatura vai ser invalida

    
    import com.itextpdf.text.pdf.PdfReader;
    import java.io.FileOutputStream;
    import java.security.cert.CertificateFactory;
    import java.io.InputStream;
    import java.security.cert.Certificate;
    import com.itextpdf.text.pdf.security.PdfPKCS7;
    import com.itextpdf.text.pdf.security.ExternalDigest;
    import java.security.MessageDigest;
    import java.security.GeneralSecurityException;
    import com.itextpdf.text.pdf.security.ExternalSignatureContainer;
    
    
    public void signExternalContainer(String certificate64, String signature64, String filePath) throws GeneralSecurityException, IOException, DocumentException, SQLException, FileNotFoundException, BusinessValidationException, BusinessValidationException, ZipValidationException, InvalidParameterException {

        PdfReader reader = new PdfReader(filePath);
        FileOutputStream os = new FileOutputStream(new File(this.getSignedName(filePath)));

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream inputStream = new ByteArrayInputStream(certificate64.getBytes());
        Certificate[] certificate = new Certificate[] { cf.generateCertificate(inputStream) };

        PdfPKCS7 sgn = new PdfPKCS7(null, certificate, "SHA256", null, getDigest(), false);
        sgn.setExternalDigest(Base64.decode(signature64.getBytes()), null, "RSA");

        byte[] encodedSig = sgn.getEncodedPKCS7(null, null, null, null, MakeSignature.CryptoStandard.CMS);

        int signatureIndex = reader.getAcroFields().getSignatureNames().size();
        ExternalSignatureContainer external = new CustomExternalSignature(encodedSig);
        MakeSignature.signDeferred(reader, DigitalSign.SIGNATURE_FIELD_PREFIX + signatureIndex, os, external);

        inputStream.close();
        os.close();
        reader.close();
    }
    
    private String getSignedName(String filePath) {
        String name = filePath;
        name = name.substring(0, name.length() - 4);
        if (!name.endsWith("_signed.pdf")) {
            name += "_signed.pdf";
        }
        File f = new File(name);
        if (!f.exists()) {
            try {
                f.createNewFile();
            } catch (IOException ex) {
                DigitalSign.log.error(ex.getMessage(), ex);
            }
        }

        return f.getAbsolutePath();
    }
    
   private static ExternalDigest getDigest() {
       return new ExternalDigest() {
            public MessageDigest getMessageDigest(String hashAlgorithm)
                    throws GeneralSecurityException {
                return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
            }
        };
    }

@ericsonmoreira
Copy link

@leandro47 o que é esse DigitalSign no código do #7 (comment)?

@leandro47
Copy link
Author

Olá, é apenas uma string private final static String SIGNATURE_FIELD_PREFIX = "Signature";

segue abaixo a classe que utilizei pra assinar:

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.List;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Utilities;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.AcroFields.FieldPosition;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalBlankSignatureContainer;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignatureContainer;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.xmp.impl.Base64;
import java.io.FileNotFoundException;
import java.sql.SQLException;
import com.itextpdf.text.pdf.security.CertificateInfo;
import com.itextpdf.text.pdf.security.CertificateInfo.X500Name;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import java.util.Calendar;

@Logic
public class DigitalSign {
    private static final Logger log = Logger.getLogger(DigitalSign.class);
    private final static String UNKNOWN_CERTIFICATE_NAME = "Unknown";
    private final static String SIGNATURE_FIELD_PREFIX = "Signature";
    private X509Certificate cert509;
    private SignatureData signatureData;

    public X509Certificate getCert509() {
        return cert509;
    }

    public void setCert509(X509Certificate cert509) {
        this.cert509 = cert509;
    }

    private String moveTemp(Integer cdFile) throws FileNotFoundException, SQLException, IOException, BusinessValidationException, ZipValidationException, InvalidParameterException {
        return AttachmentFileApi.downloadFilePath(cdFile, RelatedFileType.DEFAULT, "");
    }

    public String[] getFileToSign(Integer cdFile, String certificate64, String filePath) throws GeneralSecurityException, IOException, DocumentException, SQLException, FileNotFoundException, BusinessValidationException, BusinessValidationException, ZipValidationException, InvalidParameterException, Exception {
        String[] ret = new String[2];

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream inputStream = new ByteArrayInputStream(certificate64.getBytes());
        Certificate certificate = cf.generateCertificate(inputStream);

        String signText = "Documento assinado digitalmente \n\n"+this.getCertificateName((X509Certificate)certificate);
        File fileToSign = new File(this.getSignedName(filePath));
        PdfReader reader = new PdfReader(filePath);
        FileOutputStream os = new FileOutputStream(fileToSign);
        PdfStamper stamper = this.getPDFStamper(reader, os, fileToSign);
        PdfSignatureAppearance signatureAppearance = this.getSignatureAppearance(reader, stamper, signText, certificate);
        ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
        MakeSignature.signExternalContainer(signatureAppearance, external, (certificate64.getBytes().length * 2) + 8192);

        InputStream is = signatureAppearance.getRangeStream();
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] encodedhash = digest.digest(IOUtils.toByteArray(is));

        is.close();
        os.close();
        reader.close();

        ret[0] = fileToSign.getAbsolutePath();
        ret[1] = new String(Base64.encode(encodedhash));

        return ret;
    }

    public void signExternalContainer(String certificate64, String signature64, String filePath) throws GeneralSecurityException, IOException, DocumentException, SQLException, FileNotFoundException, BusinessValidationException, BusinessValidationException, ZipValidationException, InvalidParameterException {

        PdfReader reader = new PdfReader(filePath);
        FileOutputStream os = new FileOutputStream(new File(this.getSignedName(filePath)));

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream inputStream = new ByteArrayInputStream(certificate64.getBytes());
        Certificate[] certificate = new Certificate[] { cf.generateCertificate(inputStream) };

        PdfPKCS7 sgn = new PdfPKCS7(null, certificate, "SHA256", null, getDigest(), false);
        sgn.setExternalDigest(Base64.decode(signature64.getBytes()), null, "RSA");

        byte[] encodedSig = sgn.getEncodedPKCS7(null, null, null, null, MakeSignature.CryptoStandard.CMS);

        int signatureIndex = reader.getAcroFields().getSignatureNames().size();
        ExternalSignatureContainer external = new CustomExternalSignature(encodedSig);
        MakeSignature.signDeferred(reader, DigitalSign.SIGNATURE_FIELD_PREFIX + signatureIndex, os, external);

        inputStream.close();
        os.close();
        reader.close();
    }

    private String getCertificateName(X509Certificate cert) {
    String name = null;
    X500Name x500name = CertificateInfo.getSubjectFields(cert);
    if (x500name != null) {
            name = x500name.getField("CN");
            if (name == null) {
                name = x500name.getField("E");
            }
    }
    if (name == null) {
            name = DigitalSign.UNKNOWN_CERTIFICATE_NAME;
    }
    return name;
    }

    private static ExternalDigest getDigest() {
       return new ExternalDigest() {
            public MessageDigest getMessageDigest(String hashAlgorithm)
                    throws GeneralSecurityException {
                return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
            }
        };
    }

    private PdfSignatureAppearance getSignatureAppearance(PdfReader reader, PdfStamper stamper, String signText, Certificate cert) {
        try {
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
            appearance.setReason(this.getToken("211222"));
            appearance.setSignDate(Calendar.getInstance());
            appearance.setCertificate(cert);

            int visibleSignatures = this.getVisibleSignaturesCount(reader);
            int signatureIndex = reader.getAcroFields().getSignatureNames().size() + 1;
            int page = reader.getNumberOfPages();

            Rectangle pageBounds = reader.getCropBox(page);
            Rectangle position = this.calculateVisibleSignaturePosition(pageBounds.getRight(), pageBounds.getTop(), visibleSignatures);
            appearance.setVisibleSignature(position, page, DigitalSign.SIGNATURE_FIELD_PREFIX + signatureIndex);

            PdfTemplate layer2 = appearance.getLayer(2);
            Rectangle rect = appearance.getRect();
            this.drawSignatureBorders(visibleSignatures, layer2, rect);
            this.drawSignatureText(appearance, layer2, rect, signText);
            this.drawSignatureImage(layer2);

            return appearance;
        } catch (Exception e) {
            DigitalSign.log.error(e.getMessage(), e);
        }

        return null;
    }

    private int getVisibleSignaturesCount(PdfReader reader) {
        int visibleSignatures = 0;
        AcroFields fields = reader.getAcroFields();
        for (String name : fields.getSignatureNames()) {
            List<FieldPosition> fps = fields.getFieldPositions(name);
            if (fps != null && fps.size() > 0) {
                FieldPosition fp = fps.get(0);
                Rectangle pos = fp.position;
                if (pos.getWidth() > 0 || pos.getHeight() > 0) {
                    visibleSignatures++;
                }
            }
        }

        return visibleSignatures;
    }

    private float getSignatureHeight() {
        return Utilities.millimetersToPoints(20);
    }

    private void drawSignatureImage(final PdfTemplate layer2) throws BadElementException, MalformedURLException, IOException, DocumentException {
        float imageBorders = Utilities.millimetersToPoints(1);
        float maxImageHeight = this.getSignatureHeight();
        float maxImageWidth = this.getSignatureHeight() * 1.5f;
        Rectangle rectangle = new Rectangle(imageBorders, imageBorders, maxImageWidth - imageBorders, maxImageHeight - imageBorders);
        Image image = this.getSignatureImage();
        image.scaleToFit(rectangle);
        float imagePositionX = (maxImageWidth - image.getScaledWidth()) / 2;
        float imagePositionY = (maxImageHeight - image.getScaledHeight()) / 2;
        image.setAbsolutePosition(imagePositionX, imagePositionY);
        layer2.addImage(image);
    }

    private Image getSignatureImage() throws BadElementException, MalformedURLException, IOException {
        return Image.getInstance(this.getClass().getResource("/govbr.png").toString());
    }

    private void drawSignatureText(PdfSignatureAppearance appearance, PdfTemplate layer2, Rectangle rect, String signText) throws DocumentException {
        signText = signText += "\n" + this.getFormattedSignatureDate(appearance);
        signText = signText += "\n" + "Verifique em https://verificador.iti.br";

        Font font = new Font();
        Rectangle sr = new Rectangle(rect);
        sr.setLeft(sr.getLeft() + this.getSignatureHeight() * 1.5f);
        float size = ColumnText.fitText(font, signText, sr, 9, PdfWriter.RUN_DIRECTION_DEFAULT);
        ColumnText ct = new ColumnText(layer2);
        ct.setRunDirection(PdfWriter.RUN_DIRECTION_DEFAULT);
        ct.setSimpleColumn(new Phrase(signText, font), sr.getLeft(), sr.getBottom(), sr.getRight(), sr.getTop(), size, Element.ALIGN_LEFT);
        ct.go(true);

        ct.setSimpleColumn(new Phrase(signText, font), sr.getLeft(), sr.getBottom(), sr.getRight(), sr.getTop() - ct.getYLine() / 2, size, Element.ALIGN_LEFT);
        ct.go();
    }

    private void drawSignatureBorders(int signatureIndex, PdfTemplate layer2, Rectangle rect) {
        layer2.setColorStroke(BaseColor.DARK_GRAY);
        layer2.setLineWidth(0);

        layer2.moveTo(rect.getLeft(), rect.getTop());
        layer2.lineTo(rect.getRight(), rect.getTop());
        layer2.stroke();

        if (this.getSignatureAppearanceLine(signatureIndex) == 1) {
            layer2.moveTo(rect.getLeft(), rect.getBottom());
            layer2.lineTo(rect.getRight(), rect.getBottom());
            layer2.stroke();
        }
    }

    private String getFormattedSignatureDate(PdfSignatureAppearance appearance) {
        SimpleDateFormat sd = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss z");
        return "Data: " + sd.format(appearance.getSignDate().getTime());
    }

    private int getSignatureAppearanceLine(int signatureIndex) {
        return signatureIndex / 2 + 1;
    }

    private int getSignatureAppearanceColumn(int signatureIndex) {
        return signatureIndex % 2 + 1;
    }

    private Rectangle calculateVisibleSignaturePosition(float pageWidth, float pageHeight, int signatureIndex) {
        float pageMargins = Utilities.millimetersToPoints(10);
        float bottomMargin = Utilities.millimetersToPoints(10);
        float signatureBottomMargins = Utilities.millimetersToPoints(0);
        float signatureLeftMargins = Utilities.millimetersToPoints(2);
        int signaturesPerLine = 2;
        float signatureWidth = (pageWidth - pageMargins * 2 - signatureLeftMargins * (signaturesPerLine - 1)) / signaturesPerLine;
        int signatureColumn = this.getSignatureAppearanceColumn(signatureIndex);
        int signatureLine = this.getSignatureAppearanceLine(signatureIndex);

        float signatureLeft = pageWidth - pageMargins - signatureWidth * signatureColumn - signatureLeftMargins * (signatureColumn - 1);
        float signatureBottom = bottomMargin + this.getSignatureHeight() * (signatureLine - 1) + signatureBottomMargins * (signatureLine * 1);
        float signatureRight = signatureLeft + signatureWidth;
        float signatureTop = signatureBottom + this.getSignatureHeight();

        return new Rectangle(signatureLeft, signatureBottom, signatureRight, signatureTop);
    }

    private PdfStamper getPDFStamper(PdfReader reader, FileOutputStream fout, File fileToSign) throws Exception {
        return PdfStamper.createSignature(reader, fout, '\0', null, true);
    }


    private String getSignedName(String filePath) {
        String name = filePath;
        name = name.substring(0, name.length() - 4);
        if (!name.endsWith("_signed.pdf")) {
            name += "_signed.pdf";
        }
        File f = new File(name);
        if (!f.exists()) {
            try {
                f.createNewFile();
            } catch (IOException ex) {
                DigitalSign.log.error(ex.getMessage(), ex);
            }
        }

        return f.getAbsolutePath();
    }

    private String getToken(final String code) {
        return TermManagerFactory.getManager().getTerm(code);
    }
}

@ericsonmoreira
Copy link

@leandro47 qual a versão do iText vc está usando?

@leandro47
Copy link
Author

estou usando 5.5.9

	<dependency>
	  <groupId>com.itextpdf</groupId>
	  <artifactId>itextpdf</artifactId>
	  <version>5.5.9</version>
	</dependency>

@caduvieira
Copy link
Contributor

Essa versão possui algumas vulnerabilidades conhecidas.

https://mvnrepository.com/artifact/com.itextpdf/itextpdf/5.5.9

@leandro47
Copy link
Author

mas a versao atualizada não é compativel?

@ericsonmoreira
Copy link

Acho que não... por exemplo, a classe PdfStamper não existe na versão 7.2.3.

@caduvieira
Copy link
Contributor

O Itext7 está mais modular e, provavelmente, a troca do PdfStamper, nesse exemplo, é pelo PdfSigner.java. Essa troca depende do que é alterado. https://kb.itextpdf.com/home/it7kb/installation-guidelines/migration-guide-from-itext-5-to-itext-7 e https://stackoverflow.com/questions/45060483/whats-itext-7-equivalent-to-pdfstamper-class-in-itext-5 para outros exemplos de manipulação

@liviatsantos liviatsantos reopened this Sep 16, 2022
@israelbuiatti
Copy link

Olá, é apenas uma string private final static String SIGNATURE_FIELD_PREFIX = "Signature";

segue abaixo a classe que utilizei pra assinar:

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.List;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Utilities;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.AcroFields.FieldPosition;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalBlankSignatureContainer;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignatureContainer;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.xmp.impl.Base64;
import java.io.FileNotFoundException;
import java.sql.SQLException;
import com.itextpdf.text.pdf.security.CertificateInfo;
import com.itextpdf.text.pdf.security.CertificateInfo.X500Name;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import java.util.Calendar;

@Logic
public class DigitalSign {
    private static final Logger log = Logger.getLogger(DigitalSign.class);
    private final static String UNKNOWN_CERTIFICATE_NAME = "Unknown";
    private final static String SIGNATURE_FIELD_PREFIX = "Signature";
    private X509Certificate cert509;
    private SignatureData signatureData;

    public X509Certificate getCert509() {
        return cert509;
    }

    public void setCert509(X509Certificate cert509) {
        this.cert509 = cert509;
    }

    private String moveTemp(Integer cdFile) throws FileNotFoundException, SQLException, IOException, BusinessValidationException, ZipValidationException, InvalidParameterException {
        return AttachmentFileApi.downloadFilePath(cdFile, RelatedFileType.DEFAULT, "");
    }

    public String[] getFileToSign(Integer cdFile, String certificate64, String filePath) throws GeneralSecurityException, IOException, DocumentException, SQLException, FileNotFoundException, BusinessValidationException, BusinessValidationException, ZipValidationException, InvalidParameterException, Exception {
        String[] ret = new String[2];

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream inputStream = new ByteArrayInputStream(certificate64.getBytes());
        Certificate certificate = cf.generateCertificate(inputStream);

        String signText = "Documento assinado digitalmente \n\n"+this.getCertificateName((X509Certificate)certificate);
        File fileToSign = new File(this.getSignedName(filePath));
        PdfReader reader = new PdfReader(filePath);
        FileOutputStream os = new FileOutputStream(fileToSign);
        PdfStamper stamper = this.getPDFStamper(reader, os, fileToSign);
        PdfSignatureAppearance signatureAppearance = this.getSignatureAppearance(reader, stamper, signText, certificate);
        ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
        MakeSignature.signExternalContainer(signatureAppearance, external, (certificate64.getBytes().length * 2) + 8192);

        InputStream is = signatureAppearance.getRangeStream();
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] encodedhash = digest.digest(IOUtils.toByteArray(is));

        is.close();
        os.close();
        reader.close();

        ret[0] = fileToSign.getAbsolutePath();
        ret[1] = new String(Base64.encode(encodedhash));

        return ret;
    }

    public void signExternalContainer(String certificate64, String signature64, String filePath) throws GeneralSecurityException, IOException, DocumentException, SQLException, FileNotFoundException, BusinessValidationException, BusinessValidationException, ZipValidationException, InvalidParameterException {

        PdfReader reader = new PdfReader(filePath);
        FileOutputStream os = new FileOutputStream(new File(this.getSignedName(filePath)));

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream inputStream = new ByteArrayInputStream(certificate64.getBytes());
        Certificate[] certificate = new Certificate[] { cf.generateCertificate(inputStream) };

        PdfPKCS7 sgn = new PdfPKCS7(null, certificate, "SHA256", null, getDigest(), false);
        sgn.setExternalDigest(Base64.decode(signature64.getBytes()), null, "RSA");

        byte[] encodedSig = sgn.getEncodedPKCS7(null, null, null, null, MakeSignature.CryptoStandard.CMS);

        int signatureIndex = reader.getAcroFields().getSignatureNames().size();
        ExternalSignatureContainer external = new CustomExternalSignature(encodedSig);
        MakeSignature.signDeferred(reader, DigitalSign.SIGNATURE_FIELD_PREFIX + signatureIndex, os, external);

        inputStream.close();
        os.close();
        reader.close();
    }

    private String getCertificateName(X509Certificate cert) {
    String name = null;
    X500Name x500name = CertificateInfo.getSubjectFields(cert);
    if (x500name != null) {
            name = x500name.getField("CN");
            if (name == null) {
                name = x500name.getField("E");
            }
    }
    if (name == null) {
            name = DigitalSign.UNKNOWN_CERTIFICATE_NAME;
    }
    return name;
    }

    private static ExternalDigest getDigest() {
       return new ExternalDigest() {
            public MessageDigest getMessageDigest(String hashAlgorithm)
                    throws GeneralSecurityException {
                return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
            }
        };
    }

    private PdfSignatureAppearance getSignatureAppearance(PdfReader reader, PdfStamper stamper, String signText, Certificate cert) {
        try {
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
            appearance.setReason(this.getToken("211222"));
            appearance.setSignDate(Calendar.getInstance());
            appearance.setCertificate(cert);

            int visibleSignatures = this.getVisibleSignaturesCount(reader);
            int signatureIndex = reader.getAcroFields().getSignatureNames().size() + 1;
            int page = reader.getNumberOfPages();

            Rectangle pageBounds = reader.getCropBox(page);
            Rectangle position = this.calculateVisibleSignaturePosition(pageBounds.getRight(), pageBounds.getTop(), visibleSignatures);
            appearance.setVisibleSignature(position, page, DigitalSign.SIGNATURE_FIELD_PREFIX + signatureIndex);

            PdfTemplate layer2 = appearance.getLayer(2);
            Rectangle rect = appearance.getRect();
            this.drawSignatureBorders(visibleSignatures, layer2, rect);
            this.drawSignatureText(appearance, layer2, rect, signText);
            this.drawSignatureImage(layer2);

            return appearance;
        } catch (Exception e) {
            DigitalSign.log.error(e.getMessage(), e);
        }

        return null;
    }

    private int getVisibleSignaturesCount(PdfReader reader) {
        int visibleSignatures = 0;
        AcroFields fields = reader.getAcroFields();
        for (String name : fields.getSignatureNames()) {
            List<FieldPosition> fps = fields.getFieldPositions(name);
            if (fps != null && fps.size() > 0) {
                FieldPosition fp = fps.get(0);
                Rectangle pos = fp.position;
                if (pos.getWidth() > 0 || pos.getHeight() > 0) {
                    visibleSignatures++;
                }
            }
        }

        return visibleSignatures;
    }

    private float getSignatureHeight() {
        return Utilities.millimetersToPoints(20);
    }

    private void drawSignatureImage(final PdfTemplate layer2) throws BadElementException, MalformedURLException, IOException, DocumentException {
        float imageBorders = Utilities.millimetersToPoints(1);
        float maxImageHeight = this.getSignatureHeight();
        float maxImageWidth = this.getSignatureHeight() * 1.5f;
        Rectangle rectangle = new Rectangle(imageBorders, imageBorders, maxImageWidth - imageBorders, maxImageHeight - imageBorders);
        Image image = this.getSignatureImage();
        image.scaleToFit(rectangle);
        float imagePositionX = (maxImageWidth - image.getScaledWidth()) / 2;
        float imagePositionY = (maxImageHeight - image.getScaledHeight()) / 2;
        image.setAbsolutePosition(imagePositionX, imagePositionY);
        layer2.addImage(image);
    }

    private Image getSignatureImage() throws BadElementException, MalformedURLException, IOException {
        return Image.getInstance(this.getClass().getResource("/govbr.png").toString());
    }

    private void drawSignatureText(PdfSignatureAppearance appearance, PdfTemplate layer2, Rectangle rect, String signText) throws DocumentException {
        signText = signText += "\n" + this.getFormattedSignatureDate(appearance);
        signText = signText += "\n" + "Verifique em https://verificador.iti.br";

        Font font = new Font();
        Rectangle sr = new Rectangle(rect);
        sr.setLeft(sr.getLeft() + this.getSignatureHeight() * 1.5f);
        float size = ColumnText.fitText(font, signText, sr, 9, PdfWriter.RUN_DIRECTION_DEFAULT);
        ColumnText ct = new ColumnText(layer2);
        ct.setRunDirection(PdfWriter.RUN_DIRECTION_DEFAULT);
        ct.setSimpleColumn(new Phrase(signText, font), sr.getLeft(), sr.getBottom(), sr.getRight(), sr.getTop(), size, Element.ALIGN_LEFT);
        ct.go(true);

        ct.setSimpleColumn(new Phrase(signText, font), sr.getLeft(), sr.getBottom(), sr.getRight(), sr.getTop() - ct.getYLine() / 2, size, Element.ALIGN_LEFT);
        ct.go();
    }

    private void drawSignatureBorders(int signatureIndex, PdfTemplate layer2, Rectangle rect) {
        layer2.setColorStroke(BaseColor.DARK_GRAY);
        layer2.setLineWidth(0);

        layer2.moveTo(rect.getLeft(), rect.getTop());
        layer2.lineTo(rect.getRight(), rect.getTop());
        layer2.stroke();

        if (this.getSignatureAppearanceLine(signatureIndex) == 1) {
            layer2.moveTo(rect.getLeft(), rect.getBottom());
            layer2.lineTo(rect.getRight(), rect.getBottom());
            layer2.stroke();
        }
    }

    private String getFormattedSignatureDate(PdfSignatureAppearance appearance) {
        SimpleDateFormat sd = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss z");
        return "Data: " + sd.format(appearance.getSignDate().getTime());
    }

    private int getSignatureAppearanceLine(int signatureIndex) {
        return signatureIndex / 2 + 1;
    }

    private int getSignatureAppearanceColumn(int signatureIndex) {
        return signatureIndex % 2 + 1;
    }

    private Rectangle calculateVisibleSignaturePosition(float pageWidth, float pageHeight, int signatureIndex) {
        float pageMargins = Utilities.millimetersToPoints(10);
        float bottomMargin = Utilities.millimetersToPoints(10);
        float signatureBottomMargins = Utilities.millimetersToPoints(0);
        float signatureLeftMargins = Utilities.millimetersToPoints(2);
        int signaturesPerLine = 2;
        float signatureWidth = (pageWidth - pageMargins * 2 - signatureLeftMargins * (signaturesPerLine - 1)) / signaturesPerLine;
        int signatureColumn = this.getSignatureAppearanceColumn(signatureIndex);
        int signatureLine = this.getSignatureAppearanceLine(signatureIndex);

        float signatureLeft = pageWidth - pageMargins - signatureWidth * signatureColumn - signatureLeftMargins * (signatureColumn - 1);
        float signatureBottom = bottomMargin + this.getSignatureHeight() * (signatureLine - 1) + signatureBottomMargins * (signatureLine * 1);
        float signatureRight = signatureLeft + signatureWidth;
        float signatureTop = signatureBottom + this.getSignatureHeight();

        return new Rectangle(signatureLeft, signatureBottom, signatureRight, signatureTop);
    }

    private PdfStamper getPDFStamper(PdfReader reader, FileOutputStream fout, File fileToSign) throws Exception {
        return PdfStamper.createSignature(reader, fout, '\0', null, true);
    }


    private String getSignedName(String filePath) {
        String name = filePath;
        name = name.substring(0, name.length() - 4);
        if (!name.endsWith("_signed.pdf")) {
            name += "_signed.pdf";
        }
        File f = new File(name);
        if (!f.exists()) {
            try {
                f.createNewFile();
            } catch (IOException ex) {
                DigitalSign.log.error(ex.getMessage(), ex);
            }
        }

        return f.getAbsolutePath();
    }

    private String getToken(final String code) {
        return TermManagerFactory.getManager().getTerm(code);
    }
}

@leandro47 Nesse código você está passando a assinatura pkcs7? como você está fazendo pra preparar o arquivo (alocar o espaço da assinatura)?

A documentação pede pra seguir esses passos:

  1. Preparar o documento de assinatura

  2. Calcular quais os bytes (bytes-ranges) do arquivo preparado no passo 1 deverão entrar no computo do hash. Diferentemente da assinatura detached, o cálculo do hash para assinatura envelopadas em PDF não é o hash SHA256 do documento original (integral). É uma parte do documento preparado no passo 1.

  3. Calcular o hash SHA256 desses bytes

  4. Submeter o hash SHA256 à operação assinarPKCS7 desta API.

  5. O resultado da operação assinarPKCS7 deve ser codificado em hexadecimal e embutido no espaço que foi previamente alocado no documento no passo 1.

@leandro47
Copy link
Author

faltou essa parte do codigo ali, talvez ajude.

import java.io.InputStream;
import java.security.GeneralSecurityException;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.security.ExternalSignatureContainer;

public class CustomExternalSignature {

    private byte[] signatureContent;

    public CustomExternalSignature(byte[] signatureContent) {
        this.signatureContent = signatureContent;
    }

    @Override
    public byte[] sign(InputStream data) throws GeneralSecurityException {
        return signatureContent;
    }

    @Override
    public void modifySigningDictionary(PdfDictionary pdfDictionary) {
    }
}

@ericsonmoreira
Copy link

Pessoas, fiz uma implementação que está funcionando usando Spring Boot. Ainda está em desenvolvimento e sem documentação, mas estou trabalhando nisso. Obrigado pela ajuda de vcs.

@leandro47
Copy link
Author

leandro47 commented Sep 21, 2022

@gpieri não consegue inserir esse exemplo do @ericsonmoreira no repositorio como parte da documentação depois que ele finalizar?

@caduvieira
Copy link
Contributor

Sugiro vocês criarem um PR com o exemplo proposto e alguém do time revisa e incorpora conforme regras internas.

@ericsonmoreira
Copy link

ericsonmoreira commented Sep 27, 2022

@caduvieira vou gerar um código de exemplo e colocar em um PR. Mas tenho uma dúvida: eu crio esse PR como uma edição do arquivo que contêm a documentação? Qual a melhor maneira de fazer isso?

De qualquer forma, aqui está o link para o repositório que estamos fazendo a implementação de uma API usando Spring Boot e o iText para fazer a assinatura envelopada de documentos tanto para um único arquivo como para arquivos em lote.

@caduvieira
Copy link
Contributor

caduvieira commented Sep 27, 2022

Acho que o mais próximo do exemplo em PHP do https://manual-integracao-assinatura-eletronica.readthedocs.io/en/latest/iniciarintegracao.html#exemplo-de-aplicacao em uma nova sessão Java seria o ideal no começo. O que acha @gpieri e @liviatsantos ?

@raelAndrade
Copy link

Pessoal Bom dia!

Estou fazendo essa integração de assinatura com PDFBox em java pq a aplicação já utilizava essa biblioteca, antes estava funcionando enviando a data da assinatura com base na data do sistema operacional e do dia 14/04/2023 parou de validar minhas assinaturas isso no ambiente de homologação, já no ambiente de produção está funcionando perfeitamente as assinaturas (data do sistema operacional, com base nos exemplos da biblioteca PDFBox - signature.setSignDate(Calendar.getInstance())), minha dúvida seria essa data seria a data do sistema operacional ou a data do certificado PKCS7?

@Gabrielmoraesp
Copy link

Fala pessoal!
Alguém conseguiu avançar na assinatura digital?
Estou desenvolvendo uma plataforma voltada para licitações e preciso desenvolver um robô de lances.

Alguém consegue ajudar?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants