/* * This example showcases how to digitally sign a PDF file using a * PKCS12 (.p12/.pfx) file and LTV enable the signature by adding a second * revision to the document (containing the validation data) and a timestamp * signature (in order to protect the validation information). * * $ ./pdf_sign_ltv_timestamp_revision <FILE.p12> <P12_PASS> <INPUT_PDF_PATH> <OUTPUT_PDF_PATH> [<EXTRA_CERTS.pem>] */ package main import ( "bytes" "crypto" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "io" "io/ioutil" "log" "os" "time" "golang.org/x/crypto/pkcs12" "github.com/unidoc/unipdf/v3/annotator" "github.com/unidoc/unipdf/v3/common/license" "github.com/unidoc/unipdf/v3/core" "github.com/unidoc/unipdf/v3/model" "github.com/unidoc/unipdf/v3/model/sighandler" ) func init() { // Make sure to load your metered License API key prior to using the library. // If you need a key, you can sign up and create a free one at https://cloud.unidoc.io err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`)) if err != nil { panic(err) } } const usagef = "Usage: %s P12_FILE PASSWORD INPUT_PDF_PATH OUTPUT_PDF_PATH [EXTRA_CERTS]\n" func main() { args := os.Args if len(args) < 5 { fmt.Printf(usagef, os.Args[0]) return } p12Path := args[1] password := args[2] inputPath := args[3] outputPath := args[4] // Load private key and X509 certificate from the PKCS12 file. pfxData, err := ioutil.ReadFile(p12Path) if err != nil { log.Fatal("Fail: %v\n", err) } priv, cert, err := pkcs12.Decode(pfxData, password) if err != nil { log.Fatal("Fail: %v\n", err) } // Load certificate chain. certChain := []*x509.Certificate{cert} if len(args) == 6 { issuerCertData, err := ioutil.ReadFile(args[5]) if err != nil { log.Fatal("Fail: %v\n", err) } for len(issuerCertData) != 0 { var block *pem.Block block, issuerCertData = pem.Decode(issuerCertData) if block == nil { break } issuer, err := x509.ParseCertificate(block.Bytes) if err != nil { log.Fatal("Fail: %v\n", err) } certChain = append(certChain, issuer) } } // Sign and write file to buffer. signedBytes, err := signFile(inputPath, priv.(*rsa.PrivateKey), cert) if err != nil { log.Fatal("Fail: %v\n", err) } // LTV enable, timestamp and write file to buffer. signedBytes, err = ltvEnableAndTimestamp(bytes.NewReader(signedBytes), certChain) if err != nil { log.Fatal("Fail: %v\n", err) } // Optionally, LTV enable the applied timestamp signature. signedBytes, err = ltvEnableTimestampSig(bytes.NewReader(signedBytes)) if err != nil { log.Fatal("Fail: %v\n", err) } // Write output file to disk. err = ioutil.WriteFile(outputPath, signedBytes, 0644) if err != nil { log.Fatal("Fail: %v\n", err) } log.Printf("PDF file successfully signed. Output path: %s\n", outputPath) } func signFile(inputPath string, priv *rsa.PrivateKey, cert *x509.Certificate) ([]byte, error) { // Create reader. file, err := os.Open(inputPath) if err != nil { return nil, err } defer file.Close() reader, err := model.NewPdfReader(file) if err != nil { return nil, err } // Create appender. appender, err := model.NewPdfAppender(reader) if err != nil { return nil, err } // Create signature handler. handler, err := sighandler.NewAdobePKCS7Detached(priv, cert) if err != nil { return nil, err } // Create signature. signature := model.NewPdfSignature(handler) signature.SetName("Test Sign LTV enable") signature.SetReason("TestSignLTV") signature.SetDate(time.Now(), "") if err := signature.Initialize(); err != nil { return nil, err } // Create signature field and appearance. opts := annotator.NewSignatureFieldOpts() opts.FontSize = 10 opts.Rect = []float64{10, 25, 75, 60} field, err := annotator.NewSignatureField( signature, []*annotator.SignatureLine{ annotator.NewSignatureLine("Name", "John Doe"), annotator.NewSignatureLine("Date", "2019.16.04"), annotator.NewSignatureLine("Reason", "Signature test"), }, opts, ) field.T = core.MakeString("Test Sign LTV enable") if err = appender.Sign(1, field); err != nil { return nil, err } // Write output PDF file to buffer. buf := bytes.NewBuffer(nil) if err = appender.Write(buf); err != nil { return nil, err } return buf.Bytes(), nil } func ltvEnableAndTimestamp(r io.ReadSeeker, certChain []*x509.Certificate) ([]byte, error) { // Create reader. reader, err := model.NewPdfReader(r) if err != nil { return nil, err } // Create appender. appender, err := model.NewPdfAppender(reader) if err != nil { return nil, err } // LTV enable the certificate chain used to apply the signature. ltv, err := model.NewLTV(appender) if err != nil { return nil, err } if err := ltv.EnableAll(certChain); err != nil { return nil, err } // Create timestamp handler. handler, err := sighandler.NewDocTimeStamp("https://freetsa.org/tsr", crypto.SHA512) if err != nil { return nil, err } // Create signature field and appearance. signature := model.NewPdfSignature(handler) signature.SetName("Test Sign Timestamp") signature.SetReason("TestSignTimestamp") if err := signature.Initialize(); err != nil { return nil, err } sigField := model.NewPdfFieldSignature(signature) sigField.T = core.MakeString("Test Sign Timestamp") sigField.Rect = core.MakeArray( core.MakeInteger(0), core.MakeInteger(0), core.MakeInteger(0), core.MakeInteger(0), ) if err = appender.Sign(1, sigField); err != nil { return nil, err } // Write output PDF file to buffer. buf := bytes.NewBuffer(nil) if err = appender.Write(buf); err != nil { return nil, err } return buf.Bytes(), nil } func ltvEnableTimestampSig(r io.ReadSeeker) ([]byte, error) { // Create reader. reader, err := model.NewPdfReader(r) if err != nil { return nil, err } // Create appender. appender, err := model.NewPdfAppender(reader) if err != nil { return nil, err } // LTV enable the certificate chain used to apply the signature. ltv, err := model.NewLTV(appender) if err != nil { return nil, err } if err := ltv.EnableAll(nil); err != nil { return nil, err } // Write output PDF file to buffer. buf := bytes.NewBuffer(nil) if err = appender.Write(buf); err != nil { return nil, err } return buf.Bytes(), nil }