Sign and Configure DocMDP Restriction with Invalid Changes

This guide explains how to sign a PDF document with DocMDP restriction and additional protection against invalid changes.

Sample Input

sample PDF file

Before you begin

You should get your API key from your UniCloud account.

If this is your first time using UniPDF SDK, follow this guide to set up a local development environment.

Project setup

Clone the project repository

In your terminal, clone examples repository using the following command. It contains the Go code we will be using for this guide.

git clone https://github.com/unidoc/unipdf-examples.git

Then navigate to the signatures folder in the unipdf-examples directory.

cd unipdf-examples/signatures

Configure environment variables

For the code to work, you should configure your license key using the following command: Replace the UNIDOC_LICENSE_API_KEY with your API credentials from your UniCloud account.

Linux/Mac

export UNIDOC_LICENSE_API_KEY=PUT_YOUR_API_KEY_HERE

Windows

set UNIDOC_LICENSE_API_KEY=PUT_YOUR_API_KEY_HERE

The next section will walk through the code and explain how it works in detail.

How it works

In the example code above, the import section in lines 9-28 imports the UniPDF packages and other necessary libraries. The init function gets the metered license key from the system environment and sets it using license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`)) to authenticate the library request.

The main function, which starts in line 39, gets an input PDF document, signs the document, adds some invalid change and then validates the changes. In this function, the input and output destination are parsed form the command line arguments in lines 40-46. In lines 49-63 a new PdfReader is instantiated from the input file and the number of pages of the PDF document is obtained from the reader using totalPage, err := pdfReader.GetNumPages(). The signature is then added to the document using the addSignature function. Using the returned the buffer a new PdfReader is instantiated. In line 76, the addSomeInvalidChanges function is used to make some invalid changes on the signed document. The document is validated using ValidateFile function in line 81.

The addSomeInvalidChanges function, which is defined in lines 89-104, adds a new page using the model.PdfAppender which is created from the reader using model.NewPdfAppender(reader) in line 91.

In lines 106-141, the ValidateFile function is used to validate a given change to a PDF document. To understand how this validation is done, let’s follow the code line by line and try to see how this is achieved. In lines 107-111 a os.File is created from the input file and the possible error is handled. The deferred closure of the file is also handled in this section.

In line 113, a new PdfReader is created from the file using reader, err := model.NewPdfReader(f). The model.Permissions is obtained from the reader using reader.GetPerms() method. The next line just checks the existence of perms field and the perms.DocMDP before going on with the process. The mdp.DocMDPPermission is obtained using perms.DocMDP.GetDocMDPPermission() in lines 122. Then a slice of model.SignatureHandler is created from the DocMDPPermission using the following piece of code:

	inner_handler, _ := sighandler.NewAdobePKCS7Detached(nil, nil)
	handlerDocMdp, _ := sighandler.NewDocMDPHandler(inner_handler, docMDPPerm)

	handlers := []model.SignatureHandler{handlerDocMdp}

Following this, the document is validated using reader.ValidateSignatures(handlers)method in line 132. A list of SignatureValidationResult is returned from this method call. And finally the results are printed by iterating through each of them.

The addSignature which is used to add a signature to a document in the main function is defined in lines 143-214. The rsa.PrivateKey and x509.Certificate are generated using the generateSigKeys, another helper function which will be explained later. A new model.SignatureHandler is created from the private and public keys and then a new PdfSignature from the handler. Then in lines 183-198 the PdfFieldSignature is created using the following snippet.

opts := annotator.NewSignatureFieldOpts()
opts.FontSize = 8
opts.Rect = []float64{250, 250, 325, 300}
opts.TextColor = model.NewPdfColorDeviceRGB(255, 0, 0)

sigField, err := annotator.NewSignatureField(
  signature,
  []*annotator.SignatureLine{
    annotator.NewSignatureLine("Name", "John Doe"),
    annotator.NewSignatureLine("Date", "2019.03.14"),
    annotator.NewSignatureLine("Reason", "No reason"),
    annotator.NewSignatureLine("Location", "London"),
    annotator.NewSignatureLine("DN", "authority2:name2"),
  },
  opts,
)
if err != nil {
  return nil, err
}

sigField.T = core.MakeString("New Page Signature")

Finally, the document is signed using Sign method of the Appender object, which is created from the reader that is provided in the function parameters. Then it returns the signed document as bytes.Buffer.

The function used to generate the private and public keys is defined in lines 216-251. The RSA key pair is generated in line 220 of this function. A new x509.Certificate template is created from basic certificate information. Then using x509.CreateCertificate function a new certificate is created from the template. This function returns the certificate data as []byte type. Then the certificate is parsed using ParseCertificate function and then the private key and x509.Certificate are returned from the function.

Run the code

To run the code use the following command on your terminal.

go run pdf_sign_docmdp_invalid_changes.go <INPUT_PDF_PATH> <OUTPUT_PDF_PATH>

Sample output

2023/08/22 18:33:12 PDF file successfully signed
2023/08/22 18:33:12 == Signature 1
2023/08/22 18:33:12 Name: Test Signature Appearance Name
Date: 2023-08-22 18:33:12 +0300 UTC+0300
Reason: TestSignatureAppearance Reason
Location not specified
Contact info not specified
Fields: 1
Signed: Document is signed
Signature validation: Is invalid
Trusted: Untrusted certificate
diff is permitted: false
MDP errors:
	Pages were changed in revisions #2
	page #2 was added in revisions #2
Done

Signed with DocMDP for Invalid Changes

Got any Questions?

We're here to help you.