Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

/**
Expand Down Expand Up @@ -75,6 +77,40 @@ public final void Test02_SignatureHashAlgorithm_Default_UsesSha256() throws IOEx
"Did not expect the SHA-512 digest OID when the default is used");
}

// ==================== PDF-2253: cold-session DigiCert-timestamped verify ====================

// Reproduces PDF-2253: sign with a DigiCert RFC3161 timestamp, save, reload, and verify. The
// defect only surfaces when this is the first document operation in a fresh engine session, so
// run this method IN ISOLATION (its own JVM) to exercise the cold-session path. VerifyPdfSignatures
// must return true; the escalation saw it silently return false on the first attempts.
@Test
public final void Test06_PDF2253_DigicertTimestamp_ColdSessionVerifies() throws IOException {
final String DIGICERT_TSA = "http://timestamp.digicert.com";
final int attempts = 3;
java.util.List<String> failures = new java.util.ArrayList<>();

for (int i = 0; i < attempts; i++) {
PdfDocument pdf = PdfDocument.renderHtmlAsPdf(
"<h1>PDF-2253 DigiCert</h1><p>Attempt " + (i + 1) + "</p>");
Signature signature = new Signature(getTestFile("/Data/IronSoftware.pfx"), "123456");
signature.setTimeStampUrl(DIGICERT_TSA);
pdf.getSignature().SignPdfWithSignature(signature);

Path pdfPath = Files.createTempFile("pdf2253_digicert_" + i + "_", ".pdf");
try {
pdf.saveAs(pdfPath);
PdfDocument reloaded = PdfDocument.fromFile(pdfPath);
if (!reloaded.getSignature().VerifyPdfSignatures()) {
failures.add("Attempt " + (i + 1) + ": signature failed to verify after save+reload");
}
} finally {
Files.deleteIfExists(pdfPath);
}
}

Assertions.assertTrue(failures.isEmpty(), "PDF-2253 verification failures: " + failures);
}

// ==================== SetImageAltText ====================

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public final class Signature_Api {
public static List<VerifiedSignature> getVerifiedSignatures(InternalPdfDocument internalPdfDocument) {
RpcClient client = Access.ensureConnection();

byte[] documentBytes = PdfDocument_Api.getBytes(internalPdfDocument, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ non-blocking — why false (non-incremental) here? getBytes(doc, isIncremental) is called with false, so the server reserializes the document rather than returning its incremental bytes. For an unmodified, freshly-reloaded document (the path the new test covers) that is fine. But signature ByteRanges are byte-offset-sensitive: if getVerifiedSignatures is ever called on a document with in-memory changes (e.g. signed but not yet saved), a full reserialization can shift offsets and invalidate the very signature being verified. The sibling getBytes(doc, true) incremental form is used elsewhere in this class. Please confirm false is deliberate and that the in-memory-modified case is either impossible here or covered — the added test only exercises the reloaded-unmodified path. (bot)


final CountDownLatch finishLatch = new CountDownLatch(1);
ArrayList<PdfiumGetVerifySignatureResultP> resultChunks = new ArrayList<>();

Expand All @@ -28,7 +30,10 @@ public static List<VerifiedSignature> getVerifiedSignatures(InternalPdfDocument

requestStream.onNext(PdfiumGetVerifiedSignatureRequestStreamP.newBuilder().setInfo(infoP).build());

//don't send DataChunk (pdf byte[]) and let Server get the pdf byte[] inside the server to prevent too much grpc call
for (Iterator<byte[]> it = Utils_Util.chunk(documentBytes); it.hasNext(); ) {
requestStream.onNext(PdfiumGetVerifiedSignatureRequestStreamP.newBuilder()
.setDataChunk(ByteString.copyFrom(it.next())).build());
}

requestStream.onCompleted();

Expand Down
36 changes: 36 additions & 0 deletions ironpdf/src/test/java/com/ironsoftware/ironpdf/RCTests2026_09.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

/**
Expand Down Expand Up @@ -75,6 +77,40 @@ public final void Test02_SignatureHashAlgorithm_Default_UsesSha256() throws IOEx
"Did not expect the SHA-512 digest OID when the default is used");
}

// ==================== PDF-2253: cold-session DigiCert-timestamped verify ====================

// Reproduces PDF-2253: sign with a DigiCert RFC3161 timestamp, save, reload, and verify. The
// defect only surfaces when this is the first document operation in a fresh engine session, so
// run this method IN ISOLATION (its own JVM) to exercise the cold-session path. VerifyPdfSignatures
// must return true; the escalation saw it silently return false on the first attempts.
@Test
public final void Test06_PDF2253_DigicertTimestamp_ColdSessionVerifies() throws IOException {
final String DIGICERT_TSA = "http://timestamp.digicert.com";
final int attempts = 3;
java.util.List<String> failures = new java.util.ArrayList<>();

for (int i = 0; i < attempts; i++) {
PdfDocument pdf = PdfDocument.renderHtmlAsPdf(
"<h1>PDF-2253 DigiCert</h1><p>Attempt " + (i + 1) + "</p>");
Signature signature = new Signature(getTestFile("/Data/IronSoftware.pfx"), "123456");
signature.setTimeStampUrl(DIGICERT_TSA);
pdf.getSignature().SignPdfWithSignature(signature);

Path pdfPath = Files.createTempFile("pdf2253_digicert_" + i + "_", ".pdf");
try {
pdf.saveAs(pdfPath);
PdfDocument reloaded = PdfDocument.fromFile(pdfPath);
if (!reloaded.getSignature().VerifyPdfSignatures()) {
failures.add("Attempt " + (i + 1) + ": signature failed to verify after save+reload");
}
} finally {
Files.deleteIfExists(pdfPath);
}
}

Assertions.assertTrue(failures.isEmpty(), "PDF-2253 verification failures: " + failures);
}

// ==================== SetImageAltText ====================

@Test
Expand Down