servicosgovbr / manual-integracao-assinatura-eletronica

Manual de integração da assinatura eletrônica digital.
https://manual-integracao-assinatura-eletronica.readthedocs.io/en/latest/
Creative Commons Zero v1.0 Universal
26 stars 7 forks source link

Exemplo em java de como assinar #7

Open leandro47 opened 2 years ago

leandro47 commented 2 years ago

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

flaviossantana commented 2 years ago

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 commented 2 years ago

Viram esses exemplos? https://kb.itextpdf.com/home/it7kb/examples/digital-signatures-chapter-4

caduvieira commented 2 years ago

@gpieri teria outros exemplos?

gpieri commented 2 years ago

@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 commented 2 years ago

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

gpieri commented 2 years ago

ok, @caduvieira. Obrigado!

flaviossantana commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

@leandro47 o que é esse DigitalSign no código do https://github.com/servicosgovbr/manual-integracao-assinatura-eletronica/issues/7#issuecomment-1015343653?

leandro47 commented 1 year ago

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 commented 1 year ago

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

leandro47 commented 1 year ago

estou usando 5.5.9

    <dependency>
      <groupId>com.itextpdf</groupId>
      <artifactId>itextpdf</artifactId>
      <version>5.5.9</version>
    </dependency>
caduvieira commented 1 year ago

Essa versão possui algumas vulnerabilidades conhecidas.

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

leandro47 commented 1 year ago

mas a versao atualizada não é compativel?

ericsonmoreira commented 1 year ago

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

caduvieira commented 1 year ago

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

israelbuiatti commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

caduvieira commented 1 year ago

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

ericsonmoreira commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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?