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
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