Printer-friendly version
A printed copy of a signed document does not carry its digital signatures. A printer-friendly version makes up for it: each page shows who signed the document and a document verification code, so that whoever receives the paper can go to your website, enter the code and get the digitally signed file back.
With PKI SDK, AlphaCode generates the verification code and PdfMarker (namespace Lacuna.Pki.Pdf) writes marks, made of
text and images, on the pages of a PDF.
How it works
- The first time a document is requested, generate its verification code with
AlphaCode.Generate()and store it with the document. The code is unformatted: show it to users withAlphaCode.Format, for instance asXXXX-XXXX-XXXX-XXXX. See Generating alphanumeric codes. - Open the signed PDF with
PadesSignature.Openand read itsSigners, for instance the name on each signing certificate and the signing time. - Create a
PdfMarkerand add onePdfMarkper stamp withAddMark. - Set
ThrowIfSignedPdftofalse. By default it istrue, andPdfMarkerthrowsSignedPdfToMarkException(namespaceLacuna.Pki.Exceptions) when the PDF has any signature. - Call
WriteMarks(pdfContent), which returns the PDF with the marks. All signatures are removed from the file before the marks are applied, so the printer-friendly version is not a signed file. - On your verification page, turn the code the user typed back into the stored form with
AlphaCode.Parse, look up the document and validate its signatures as usual, withPadesSignature.OpenandValidateSignature. The sample waits two seconds before answering an unknown code, to slow down brute-force attempts.
Example
Adapted from the sample: a summary on the bottom margin of every page, and a new page at the end of the document with the verification code.
var formattedCode = AlphaCode.Format(verificationCode);
// Names of the signers, for the summary
var signature = PadesSignature.Open(pdfContent);
var signerNames = string.Join(", ", signature.Signers.Select(s => s.Signer.SigningCertificate.SubjectName.CommonName));
var summary = $"This document was digitally signed by {signerNames}.\nTo verify the signatures go to {verificationSite} and inform the code {formattedCode}";
var pdfMarker = new PdfMarker();
// Summary on the bottom margin of every page
pdfMarker.AddMark(new PdfMark() {
PageOption = PdfMarkPageOptions.AllPages,
Container = new PadesVisualRectangle() { Height = 2, Bottom = 0, Left = 1.5, Right = 3.5 },
Elements = new List<PdfMarkElement>() {
new PdfMarkText() {
Texts = new List<PdfTextSection>() {
new PdfTextSection() { Style = PdfTextStyle.Normal, Text = summary }
}
}
}
});
// New page at the end of the document, with 1-inch margins
var summaryPage = new PdfMark() {
PageOption = PdfMarkPageOptions.NewPage,
Container = new PadesVisualRectangle() { Top = 2.54, Bottom = 2.54, Left = 2.54, Right = 2.54 }
};
summaryPage.Elements.Add(new PdfMarkText() {
RelativeContainer = new PadesVisualRectangle() { Top = 0, Height = 2, Left = 0, Right = 0 },
Align = PadesHorizontalAlign.Center,
Texts = new List<PdfTextSection>() {
new PdfTextSection() { Style = PdfTextStyle.Normal, FontSize = 14, Text = $"Verification code: {formattedCode}" }
}
});
pdfMarker.AddMark(summaryPage);
pdfMarker.ThrowIfSignedPdf = false; // the PDF is signed: don't throw SignedPdfToMarkException
var pfvContent = pdfMarker.WriteMarks(pdfContent);
The sample's summary page also has the ICP-Brasil logo, a QR code of the verification link (a PNG made with the QRCoder
library), and one line per signer with a green or red icon for the result of ValidateSignature. It also puts the summary
on the right margin of every page, rotated with PdfMarkRotation.D90 so that the text reads upward.
Marks
A PdfMark is a stamp applied on the pages of the PDF:
| Property | What it sets |
|---|---|
PageOption | The pages that get the mark, a PdfMarkPageOptions: AllPages; NewPage, a new page added at the end of the document; or SinglePage and SinglePageFromEnd, together with the PageOptionNumber property. |
Container | The position of the mark on the page, a PadesVisualRectangle: the class that positions signature visual representations, explained in Defining a container. |
MeasurementUnits | PadesMeasurementUnits.Centimeters or PadesMeasurementUnits.PdfPoints. The sample does not set it, and its measures are in centimeters (2.54 for 1-inch margins). |
Elements | The text and images of the mark: a list of PdfMarkText and PdfMarkImage. |
BorderWidth, BorderColor, BackgroundColor | The border and the background of the mark. |
Every element has a RelativeContainer, its position inside the mark's Container; a Rotation, D0, D90, D180 or
D270 (in degrees); and an Opacity, 100 (opaque) by default. Then:
PdfMarkTexthasTexts, a list ofPdfTextSection, each with itsText,Style(Normal,Bold,ItalicorUnderline, which can be combined),FontSizeandColor(black by default); andAlign,VerticalAlignandLineSpacing, a multiplier of the font size.PdfMarkImagehasImageContent, the bytes of the image; the sample uses PNG images.
More options
PreserveSignaturesVisualRepresentation: keep the images of the signatures' visual representations on the PDF, when present.NewPagesTemplate: aPdfPageTemplatewith the size and orientation of the pages thatNewPagemarks create. If not set, the most common paper size of the PDF's pages is used, in portrait.CreateContextandComputeRequiredArea: compute the area that a text needs before you place it, then callWriteMarks(context).WriteMarks(Stream, Stream)reads the PDF from a stream and writes the result to another.
CAdES signatures
A CMS/CAdES file is not a PDF, so the CAdES sample builds the printer-friendly version as a new document: it reads the
signers with CadesSignature.Open and writes the summary mark on a blank PDF that ships with the sample.