perf(pdf): use random access read and streaming for large pdf signing
This commit is contained in:
+112
-20
@@ -10,6 +10,9 @@ import br.com.cybercore.signer.core.protocol.CcspErrorCode;
|
|||||||
import br.com.cybercore.signer.core.provider.CertificateManager;
|
import br.com.cybercore.signer.core.provider.CertificateManager;
|
||||||
import br.com.cybercore.signer.core.provider.CertificateProviderException;
|
import br.com.cybercore.signer.core.provider.CertificateProviderException;
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.io.RandomAccessRead;
|
||||||
|
import org.apache.pdfbox.io.RandomAccessReadBuffer;
|
||||||
|
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
@@ -83,7 +86,19 @@ public class PdfSigningService {
|
|||||||
*/
|
*/
|
||||||
public static int getPageCount(byte[] pdfBytes) throws IOException {
|
public static int getPageCount(byte[] pdfBytes) throws IOException {
|
||||||
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
||||||
try (PDDocument doc = Loader.loadPDF(pdfBytes)) {
|
try (RandomAccessReadBuffer rar = new RandomAccessReadBuffer(pdfBytes);
|
||||||
|
PDDocument doc = Loader.loadPDF(rar)) {
|
||||||
|
return doc.getNumberOfPages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna a quantidade total de páginas contidas no arquivo PDF.
|
||||||
|
*/
|
||||||
|
public static int getPageCount(File pdfFile) throws IOException {
|
||||||
|
Objects.requireNonNull(pdfFile, "Arquivo PDF não pode ser nulo.");
|
||||||
|
try (RandomAccessReadBufferedFile rar = new RandomAccessReadBufferedFile(pdfFile);
|
||||||
|
PDDocument doc = Loader.loadPDF(rar)) {
|
||||||
return doc.getNumberOfPages();
|
return doc.getNumberOfPages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +108,24 @@ public class PdfSigningService {
|
|||||||
*/
|
*/
|
||||||
public static PageDimensions getPageDimensions(byte[] pdfBytes, int pageIndex0Based) throws IOException {
|
public static PageDimensions getPageDimensions(byte[] pdfBytes, int pageIndex0Based) throws IOException {
|
||||||
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
||||||
try (PDDocument doc = Loader.loadPDF(pdfBytes)) {
|
try (RandomAccessReadBuffer rar = new RandomAccessReadBuffer(pdfBytes);
|
||||||
|
PDDocument doc = Loader.loadPDF(rar)) {
|
||||||
|
if (pageIndex0Based < 0 || pageIndex0Based >= doc.getNumberOfPages()) {
|
||||||
|
throw new IllegalArgumentException("Índice de página inválido: " + pageIndex0Based + ". Total: " + doc.getNumberOfPages());
|
||||||
|
}
|
||||||
|
PDPage page = doc.getPage(pageIndex0Based);
|
||||||
|
PDRectangle mediaBox = page.getMediaBox();
|
||||||
|
return new PageDimensions(mediaBox.getWidth(), mediaBox.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna as dimensões em pontos (pt) da página informada do arquivo PDF.
|
||||||
|
*/
|
||||||
|
public static PageDimensions getPageDimensions(File pdfFile, int pageIndex0Based) throws IOException {
|
||||||
|
Objects.requireNonNull(pdfFile, "Arquivo PDF não pode ser nulo.");
|
||||||
|
try (RandomAccessReadBufferedFile rar = new RandomAccessReadBufferedFile(pdfFile);
|
||||||
|
PDDocument doc = Loader.loadPDF(rar)) {
|
||||||
if (pageIndex0Based < 0 || pageIndex0Based >= doc.getNumberOfPages()) {
|
if (pageIndex0Based < 0 || pageIndex0Based >= doc.getNumberOfPages()) {
|
||||||
throw new IllegalArgumentException("Índice de página inválido: " + pageIndex0Based + ". Total: " + doc.getNumberOfPages());
|
throw new IllegalArgumentException("Índice de página inválido: " + pageIndex0Based + ". Total: " + doc.getNumberOfPages());
|
||||||
}
|
}
|
||||||
@@ -108,7 +140,8 @@ public class PdfSigningService {
|
|||||||
*/
|
*/
|
||||||
public static BufferedImage renderPageImage(byte[] pdfBytes, int pageIndex0Based, float dpi) throws IOException {
|
public static BufferedImage renderPageImage(byte[] pdfBytes, int pageIndex0Based, float dpi) throws IOException {
|
||||||
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
||||||
try (PDDocument doc = Loader.loadPDF(pdfBytes)) {
|
try (RandomAccessReadBuffer rar = new RandomAccessReadBuffer(pdfBytes);
|
||||||
|
PDDocument doc = Loader.loadPDF(rar)) {
|
||||||
if (pageIndex0Based < 0 || pageIndex0Based >= doc.getNumberOfPages()) {
|
if (pageIndex0Based < 0 || pageIndex0Based >= doc.getNumberOfPages()) {
|
||||||
throw new IllegalArgumentException("Índice de página inválido: " + pageIndex0Based + ". Total: " + doc.getNumberOfPages());
|
throw new IllegalArgumentException("Índice de página inválido: " + pageIndex0Based + ". Total: " + doc.getNumberOfPages());
|
||||||
}
|
}
|
||||||
@@ -128,23 +161,55 @@ public class PdfSigningService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assina digitalmente um arquivo PDF do sistema de arquivos e grava o resultado no arquivo de destino.
|
* Assina digitalmente um arquivo PDF do sistema de arquivos e grava o resultado no arquivo de destino,
|
||||||
|
* utilizando streaming direto via RandomAccessReadBufferedFile para minimizar o consumo de memória heap.
|
||||||
*/
|
*/
|
||||||
public void signPdfFile(File sourcePdf, File targetPdf, PdfSignatureOptions options) throws Exception {
|
public void signPdfFile(File sourcePdf, File targetPdf, PdfSignatureOptions options) throws Exception {
|
||||||
Objects.requireNonNull(sourcePdf, "Arquivo PDF de origem não pode ser nulo.");
|
Objects.requireNonNull(sourcePdf, "Arquivo PDF de origem não pode ser nulo.");
|
||||||
Objects.requireNonNull(targetPdf, "Arquivo PDF de destino não pode ser nulo.");
|
Objects.requireNonNull(targetPdf, "Arquivo PDF de destino não pode ser nulo.");
|
||||||
Objects.requireNonNull(options, "Opções de assinatura não podem ser nulas.");
|
Objects.requireNonNull(options, "Opções de assinatura não podem ser nulas.");
|
||||||
|
|
||||||
byte[] inputBytes = Files.readAllBytes(sourcePdf.toPath());
|
if (!sourcePdf.exists() || !sourcePdf.isFile()) {
|
||||||
byte[] signedBytes = signPdf(inputBytes, options);
|
throw new IllegalArgumentException("Arquivo PDF de origem não existe: " + sourcePdf.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
if (targetPdf.getParentFile() != null) {
|
boolean sameFile = sourcePdf.getCanonicalPath().equals(targetPdf.getCanonicalPath());
|
||||||
targetPdf.getParentFile().mkdirs();
|
File parentDir = targetPdf.getParentFile();
|
||||||
|
if (parentDir != null && !parentDir.exists()) {
|
||||||
|
parentDir.mkdirs();
|
||||||
}
|
}
|
||||||
try (FileOutputStream fos = new FileOutputStream(targetPdf)) {
|
|
||||||
fos.write(signedBytes);
|
File effectiveTarget = sameFile
|
||||||
|
? Files.createTempFile("cybercore_signed_", ".pdf").toFile()
|
||||||
|
: targetPdf;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Calcula o hash SHA-256 via streaming em blocos de 64KB sem carregar o arquivo na RAM
|
||||||
|
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
|
try (InputStream is = Files.newInputStream(sourcePdf.toPath())) {
|
||||||
|
byte[] buffer = new byte[65536];
|
||||||
|
int read;
|
||||||
|
while ((read = is.read(buffer)) != -1) {
|
||||||
|
sha256.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] pdfDigest = sha256.digest();
|
||||||
|
|
||||||
|
try (RandomAccessReadBufferedFile rar = new RandomAccessReadBufferedFile(sourcePdf);
|
||||||
|
FileOutputStream fos = new FileOutputStream(effectiveTarget)) {
|
||||||
|
signPdfInternal(rar, fos, options, pdfDigest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sameFile) {
|
||||||
|
Files.move(effectiveTarget.toPath(), targetPdf.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (sameFile && effectiveTarget.exists()) {
|
||||||
|
Files.deleteIfExists(effectiveTarget.toPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.info("PDF assinado com sucesso e salvo em: {}", targetPdf.getAbsolutePath());
|
|
||||||
|
logger.info("PDF assinado com sucesso e salvo em: {}", targetPdf.getAbsolutePath().replaceAll("[\r\n]", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,6 +220,39 @@ public class PdfSigningService {
|
|||||||
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
Objects.requireNonNull(pdfBytes, "Bytes do PDF não podem ser nulos.");
|
||||||
Objects.requireNonNull(options, "Opções de assinatura não podem ser nulas.");
|
Objects.requireNonNull(options, "Opções de assinatura não podem ser nulas.");
|
||||||
|
|
||||||
|
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] pdfDigest = sha256.digest(pdfBytes);
|
||||||
|
|
||||||
|
// Se o PDF for muito grande (> 2MB), usamos um arquivo temporário com RandomAccessReadBufferedFile
|
||||||
|
// para aliviar significativamente a pressão no Garbage Collector e economizar memória heap.
|
||||||
|
if (pdfBytes.length > 2 * 1024 * 1024) {
|
||||||
|
File tempSource = Files.createTempFile("cybercore_in_", ".pdf").toFile();
|
||||||
|
File tempTarget = Files.createTempFile("cybercore_out_", ".pdf").toFile();
|
||||||
|
try {
|
||||||
|
Files.write(tempSource.toPath(), pdfBytes);
|
||||||
|
try (RandomAccessReadBufferedFile rar = new RandomAccessReadBufferedFile(tempSource);
|
||||||
|
FileOutputStream fos = new FileOutputStream(tempTarget)) {
|
||||||
|
signPdfInternal(rar, fos, options, pdfDigest);
|
||||||
|
}
|
||||||
|
return Files.readAllBytes(tempTarget.toPath());
|
||||||
|
} finally {
|
||||||
|
Files.deleteIfExists(tempSource.toPath());
|
||||||
|
Files.deleteIfExists(tempTarget.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Para PDFs de tamanho padrão (< 2MB), processa em memória com buffer pré-alocado
|
||||||
|
ByteArrayOutputStream finalOut = new ByteArrayOutputStream(pdfBytes.length + 65536);
|
||||||
|
try (RandomAccessReadBuffer rar = new RandomAccessReadBuffer(pdfBytes)) {
|
||||||
|
signPdfInternal(rar, finalOut, options, pdfDigest);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Assinatura PAdES concluída com sucesso. Tamanho final: {} bytes", finalOut.size());
|
||||||
|
return finalOut.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signPdfInternal(RandomAccessRead rar, OutputStream targetOut,
|
||||||
|
PdfSignatureOptions options, byte[] pdfDigest) throws Exception {
|
||||||
// 1. Localiza o certificado e o material criptográfico (chave e cadeia)
|
// 1. Localiza o certificado e o material criptográfico (chave e cadeia)
|
||||||
Optional<ManagedCertificate> certOpt = certificateManager.findById(options.certificateId());
|
Optional<ManagedCertificate> certOpt = certificateManager.findById(options.certificateId());
|
||||||
if (certOpt.isEmpty()) {
|
if (certOpt.isEmpty()) {
|
||||||
@@ -186,8 +284,6 @@ public class PdfSigningService {
|
|||||||
String rawName = cert.commonName() != null ? cert.commonName() : cert.alias();
|
String rawName = cert.commonName() != null ? cert.commonName() : cert.alias();
|
||||||
String signerName = X509CertificateUtils.extractHolderName(rawName);
|
String signerName = X509CertificateUtils.extractHolderName(rawName);
|
||||||
|
|
||||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
|
||||||
byte[] pdfDigest = sha256.digest(pdfBytes);
|
|
||||||
String hashShort = bytesToHex(pdfDigest).substring(0, 16).toUpperCase();
|
String hashShort = bytesToHex(pdfDigest).substring(0, 16).toUpperCase();
|
||||||
|
|
||||||
PdfSealTheme theme = options.sealTheme();
|
PdfSealTheme theme = options.sealTheme();
|
||||||
@@ -213,8 +309,7 @@ public class PdfSigningService {
|
|||||||
// preservando intactas quaisquer assinaturas digitais pré-existentes. Em conformidade com a ISO 32000-1 / ETSI PAdES,
|
// preservando intactas quaisquer assinaturas digitais pré-existentes. Em conformidade com a ISO 32000-1 / ETSI PAdES,
|
||||||
// o selo visual é associado diretamente ao widget da assinatura (/Widget /AP /N) sem alterar o stream /Contents
|
// o selo visual é associado diretamente ao widget da assinatura (/Widget /AP /N) sem alterar o stream /Contents
|
||||||
// das páginas existentes, garantindo que o validador do ITI aprove a verificação incremental sem indeterminação.
|
// das páginas existentes, garantindo que o validador do ITI aprove a verificação incremental sem indeterminação.
|
||||||
ByteArrayOutputStream finalOut = new ByteArrayOutputStream();
|
try (PDDocument doc = Loader.loadPDF(rar)) {
|
||||||
try (PDDocument doc = Loader.loadPDF(pdfBytes)) {
|
|
||||||
PDSignature signature = new PDSignature();
|
PDSignature signature = new PDSignature();
|
||||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
||||||
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
||||||
@@ -235,16 +330,13 @@ public class PdfSigningService {
|
|||||||
signatureOptions.setVisualSignature(visualTemplate);
|
signatureOptions.setVisualSignature(visualTemplate);
|
||||||
signatureOptions.setPage(targetPage);
|
signatureOptions.setPage(targetPage);
|
||||||
doc.addSignature(signature, signer, signatureOptions);
|
doc.addSignature(signature, signer, signatureOptions);
|
||||||
doc.saveIncremental(finalOut);
|
doc.saveIncremental(targetOut);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
doc.addSignature(signature, signer);
|
doc.addSignature(signature, signer);
|
||||||
doc.saveIncremental(finalOut);
|
doc.saveIncremental(targetOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Assinatura PAdES concluída com sucesso. Tamanho final: {} bytes", finalOut.size());
|
|
||||||
return finalOut.toByteArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int resolveTargetPage(PDDocument doc, PdfSignatureOptions options) {
|
private int resolveTargetPage(PDDocument doc, PdfSignatureOptions options) {
|
||||||
|
|||||||
+114
@@ -289,4 +289,118 @@ class PdfSigningServiceTest {
|
|||||||
assertThat(validationResult.signers().get(1).digestValid()).isTrue();
|
assertThat(validationResult.signers().get(1).digestValid()).isTrue();
|
||||||
assertThat(validationResult.signers().get(1).signerName()).contains("Maria Souza");
|
assertThat(validationResult.signers().get(1).signerName()).contains("Maria Souza");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Deve assinar arquivo PDF via signPdfFile com arquivos de origem e destino distintos")
|
||||||
|
void shouldSignPdfFileWithDistinctSourceAndTarget() throws Exception {
|
||||||
|
java.io.File src = java.io.File.createTempFile("cybercore_src_", ".pdf");
|
||||||
|
java.io.File tgt = java.io.File.createTempFile("cybercore_tgt_", ".pdf");
|
||||||
|
try {
|
||||||
|
java.nio.file.Files.write(src.toPath(), createSamplePdf());
|
||||||
|
|
||||||
|
PdfSignatureOptions options = PdfSignatureOptions.builder(testCertId)
|
||||||
|
.visibleSeal(true)
|
||||||
|
.position(PdfSealPosition.BOTTOM_RIGHT)
|
||||||
|
.reason("Assinatura de Arquivo Físico")
|
||||||
|
.location("Brasília, DF")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
pdfSigningService.signPdfFile(src, tgt, options);
|
||||||
|
|
||||||
|
assertThat(tgt.exists()).isTrue();
|
||||||
|
assertThat(tgt.length()).isGreaterThan(src.length());
|
||||||
|
|
||||||
|
// Valida leitura de páginas e dimensões a partir de File
|
||||||
|
assertThat(PdfSigningService.getPageCount(src)).isEqualTo(1);
|
||||||
|
assertThat(PdfSigningService.getPageCount(tgt)).isEqualTo(1);
|
||||||
|
PdfSigningService.PageDimensions dims = PdfSigningService.getPageDimensions(tgt, 0);
|
||||||
|
assertThat(dims.width()).isGreaterThan(0);
|
||||||
|
assertThat(dims.height()).isGreaterThan(0);
|
||||||
|
|
||||||
|
// Validação criptográfica do arquivo resultante
|
||||||
|
byte[] signedBytes = java.nio.file.Files.readAllBytes(tgt.toPath());
|
||||||
|
SignatureValidationService validationService = new SignatureValidationService();
|
||||||
|
ValidationResult res = validationService.validateAttached(signedBytes, "arquivo_assinado.pdf");
|
||||||
|
assertThat(res.isValid()).isTrue();
|
||||||
|
} finally {
|
||||||
|
java.nio.file.Files.deleteIfExists(src.toPath());
|
||||||
|
java.nio.file.Files.deleteIfExists(tgt.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Deve assinar arquivo PDF in-place (mesmo arquivo de origem e destino)")
|
||||||
|
void shouldSignInPlaceWhenSourceAndTargetAreSameFile() throws Exception {
|
||||||
|
java.io.File file = java.io.File.createTempFile("cybercore_inplace_", ".pdf");
|
||||||
|
try {
|
||||||
|
byte[] sample = createSamplePdf();
|
||||||
|
java.nio.file.Files.write(file.toPath(), sample);
|
||||||
|
long initialSize = file.length();
|
||||||
|
|
||||||
|
PdfSignatureOptions options = PdfSignatureOptions.builder(testCertId)
|
||||||
|
.visibleSeal(false)
|
||||||
|
.reason("Assinatura In-Place")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
pdfSigningService.signPdfFile(file, file, options);
|
||||||
|
|
||||||
|
assertThat(file.exists()).isTrue();
|
||||||
|
assertThat(file.length()).isGreaterThan(initialSize);
|
||||||
|
|
||||||
|
byte[] signedBytes = java.nio.file.Files.readAllBytes(file.toPath());
|
||||||
|
SignatureValidationService validationService = new SignatureValidationService();
|
||||||
|
ValidationResult res = validationService.validateAttached(signedBytes, "inplace.pdf");
|
||||||
|
assertThat(res.isValid()).isTrue();
|
||||||
|
} finally {
|
||||||
|
java.nio.file.Files.deleteIfExists(file.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Deve assinar PDF grande (> 2MB) acionando a estratégia de arquivo temporário")
|
||||||
|
void shouldSignLargePdfTriggeringTempFileOptimization() throws Exception {
|
||||||
|
// Cria PDF com buffer grande embutido para ultrapassar 2MB
|
||||||
|
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||||
|
try (PDDocument doc = new PDDocument()) {
|
||||||
|
PDPage page = new PDPage();
|
||||||
|
doc.addPage(page);
|
||||||
|
try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
|
||||||
|
cs.beginText();
|
||||||
|
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 10);
|
||||||
|
cs.newLineAtOffset(50, 700);
|
||||||
|
cs.showText("PDF com carga útil volumosa para teste de threshold de streaming.");
|
||||||
|
cs.endText();
|
||||||
|
}
|
||||||
|
// Anexa stream arbitrário para elevar o tamanho acima de 2MB
|
||||||
|
org.apache.pdfbox.pdmodel.common.PDStream stream = new org.apache.pdfbox.pdmodel.common.PDStream(doc);
|
||||||
|
try (java.io.OutputStream os = stream.createOutputStream()) {
|
||||||
|
byte[] chunk = new byte[65536];
|
||||||
|
java.util.Arrays.fill(chunk, (byte) 'X');
|
||||||
|
for (int i = 0; i < 35; i++) { // ~2.2 MB
|
||||||
|
os.write(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doc.getDocumentCatalog().getCOSObject().setItem(
|
||||||
|
org.apache.pdfbox.cos.COSName.getPDFName("LargePayload"), stream.getCOSObject()
|
||||||
|
);
|
||||||
|
doc.save(baos);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] largePdf = baos.toByteArray();
|
||||||
|
assertThat(largePdf.length).isGreaterThan(2 * 1024 * 1024);
|
||||||
|
|
||||||
|
PdfSignatureOptions options = PdfSignatureOptions.builder(testCertId)
|
||||||
|
.visibleSeal(true)
|
||||||
|
.position(PdfSealPosition.BOTTOM_RIGHT)
|
||||||
|
.reason("Assinatura de Arquivo Grande")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
byte[] signed = pdfSigningService.signPdf(largePdf, options);
|
||||||
|
assertThat(signed).isNotNull();
|
||||||
|
assertThat(signed.length).isGreaterThan(largePdf.length);
|
||||||
|
|
||||||
|
SignatureValidationService validationService = new SignatureValidationService();
|
||||||
|
ValidationResult res = validationService.validateAttached(signed, "large_signed.pdf");
|
||||||
|
assertThat(res.isValid()).isTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user