pdfsign

repository·main·Indexed 41 days ago

https://github.com/digitorus/pdfsign

A Go library and CLI tool for digitally signing and verifying PDF documents. It provides a fluent API for signature placement, visual appearance customization with TTF fonts, PDF form field manipulation, and signature extraction. The library supports various cryptographic settings, including custom hash algorithms, external signers (HSM, KMS), and post-quantum ML-DSA signing using the PAdES_B_T format.

Tokens
16K
Snippets
62
Records
76
Agent score
52%

What's inside pdfsign

  1. Understand the pdfsign package architecture

    main

    The library is organized into specialized subpackages. While most users interact with the root pdfsign package, understanding the subpackages helps in knowing where specific functionality resides:

    • pdfsign: The primary entry point providing the fluent API for signing, verification, and document management.
    • extract: Low-level signature inspection (extracting PKCS#7 envelopes and signed data).
    • forms: PDF form handling (field discovery and object updates).
    • initials: Configuration and placement logic for signing initials across multiple pages.
    • signers/...: External Signers for remote signing (e.g., CSC, KMS, PKCS#11).
    • fonts: Font resource management and TTF metric parsing.
    • images: Image resource handling for visual signatures.
    • internal/...: Private implementation details.
  2. Integrate with external Cloud and HSM signers

    main

    The project provides standalone modules for integrating with various external signing services. These are designed to be imported only when needed to keep core dependencies light.

    Supported integrations include:

    • CSC (Cloud Signature Consortium): Remote cloud signing services.
    • AWS KMS: Using github.com/digitorus/pdfsign/signers/aws.
    • GCP KMS: Using github.com/digitorus/pdfsign/signers/gcp.
    • Azure Key Vault: Using github.com/digitorus/pdfsign/signers/azure.
    • PKCS#11 / HSM: Using github.com/digitorus/pdfsign/signers/pkcs11.

    All these implementations satisfy the crypto.Signer interface.

    // Example: AWS KMS Signer
    // client is a *kms.Client from aws-sdk-go-v2
    awsSigner, _ := aws.NewSigner(client, "key-id-or-arn", publicKey)
    doc.Sign(awsSigner, cert)
    
    // Example: Cloud Signature Consortium (CSC)
    import "github.com/digitorus/pdfsign/signers/csc"
    
    signer, _ := csc.NewSigner(csc.Config{
        BaseURL:      "https://signing-service.example.com/csc/v1",
        CredentialID: "my-signing-key",
        AuthToken:    "Bearer ey...",
        PIN:          "123456",
    })
    doc.Sign(signer, cert).Reason("Cloud Signed")
  3. Quick Start with pdfsign CLI

    main

    The pdfsign CLI tool allows you to sign and verify PDF documents directly from the terminal.

    Sign a PDF: Requires an input file, output destination, certificate, and private key.

    Verify a PDF: Checks the validity of signatures within a document.

    # Sign a PDF
    ./pdfsign sign -name "John Doe" input.pdf output.pdf certificate.crt private_key.key
    
    # Verify a PDF signature
    ./pdfsign verify document.pdf
    #!/bin/bash
    ./pdfsign sign -name "John Doe" input.pdf output.pdf certificate.crt private_key.key
    ./pdfsign verify document.pdf
  4. Customize signature appearance and fonts

    main

    You can embed TrueType fonts (TTF) for custom signature styling. The library automatically parses font metrics for accurate text positioning.

    To create a custom appearance:

    1. Load TTF data using os.ReadFile.
    2. Register the font with doc.AddFont(name, data).
    3. Create an appearance with pdfsign.NewAppearance(width, height).
    4. Use .Text(string), .Font(font, size), and .Center() to style the text.

    Alternatively, use the built-in Standard() appearance which automatically includes the Signer Name, Reason, Location, and Date using template variables ({{Name}}, {{Reason}}, {{Location}}, {{Date}}).

    // Custom Font Example
    fontData, _ := os.ReadFile("MySignatureFont.ttf")
    customFont := doc.AddFont("MySig", fontData)
    
    appearance := pdfsign.NewAppearance(200, 80)
    appearance.Text("John Doe").Font(customFont, 24).Center()
    
    // Standard Appearance Example
    // Standard appearance with Name, Reason, Location, and Date
    appearance := pdfsign.NewAppearance(300, 100).Standard()
    
    doc.Sign(signer, cert).
        SignerName("John Doe").
        Reason("Contract Agreement").
        Location("Amsterdam, NL").
        Appearance(appearance, 100, 100)
  5. Set up the DSS Validator Test Infrastructure

    main

    To run integration tests using the EU DSS (Digital Signature Services) validator, you must first set up the local testing environment using the provided script. This script builds a Docker image containing the DSS Webapp and starts the container on port 8080.

    Run the following command from the repository root:

    ./scripts/setup-dss.sh
  6. Perform Post-Quantum-only (ML-DSA) signing

    main

    To achieve a post-quantum-only signature path, every component in the chain must use ML-DSA: the PDF signer, the issuing CA chain, CRL/OCSP responders, and the RFC 3161 TSA chain.

    When using an ML-DSA signer, the library selects SHA-512 automatically to comply with RFC 9882. You can specify it explicitly to make the policy visible.

    Requirements:

    • privateKey must be an *mldsa.PrivateKey.
    • certificate and intermediates must use ML-DSA.
    • pqcTSAURL must serve ML-DSA-signed timestamp tokens.
    • Use pdfsign.PAdES_B_T format.
    doc, err := pdfsign.OpenFile("contract.pdf")
    if err != nil {
        log.Fatal(err)
    }
    
    // PAdES B-T adds an RFC 3161 signature-time-stamp. SHA-512 is used as the
    // interoperable RFC 9882 CMS digest for all supported ML-DSA parameter sets.
    doc.Sign(privateKey, certificate, intermediates...).
        Format(pdfsign.PAdES_B_T).
        Digest(crypto.SHA512).
        Timestamp(pqcTSAURL).
        Reason("Post-quantum approval")
    
    output, err := os.Create("contract-pqc-signed.pdf")
    if err != nil {
        log.Fatal(err)
    }
    defer output.Close()
    if _, err := doc.Write(output); err != nil {
        log.Fatal(err)
    }
  7. Set up DSS for local validation testing

    main

    To run automated signature validation tests against European standards, you must set up a local Digital Signature Service (DSS) instance. The project provides a script to handle this via Docker or Apple's container CLI.

    Note for Apple Silicon (M1/M2/M3): If using the container tool, you must have Rosetta 2 installed: softwareupdate --install-rosetta --agree-to-license

    Setup Steps:

    1. Run the setup script:
      ./scripts/setup-dss.sh
    2. Once the service is running on http://localhost:8080, you can run the validation tests:
      export DSS_API_URL=http://localhost:8080/services/rest/validation/v2/validateSignature
      go test -v ./sign -run TestSignDSSValidation
  8. Configure signature types and permissions

    main

    When signing a PDF, you can specify the SignatureType to define the purpose of the signature and, for certification signatures, the level of document modification allowed via Permission.

    Signature Types

    • ApprovalSignature: The most common type; indicates the signer approves the content.
    • CertificationSignature: Indicates the signer is the author and allows specifying permitted changes.
    • DocumentTimestamp: A document-level timestamp proving existence at a specific time.

    Permissions (for CertificationSignature only)

    • NoChanges: Guarantees no modifications are allowed.
    • AllowFormFilling: Permits form filling and signing, but no comments/annotations.
    • AllowFormFillingAndAnnotations: Permits form filling, signing, and adding comments/annotations.
  9. Use the csc.Signer for remote signing

    main

    The csc.Signer type implements the standard Go crypto.Signer interface, allowing it to be used with any library that expects a cryptographic signer (such as pdfsign).

    It performs remote signing by sending a hash of the data to the CSC service via the signatures/signHash endpoint. If the service requires authorization, the signer will automatically attempt to obtain Signature Activation Data (SAD) using the provided PIN or OTP before signing.

    // Example integration with a PDF signing workflow
    signer, _ := csc.NewSigner(csc.Config{
        BaseURL:      "https://signing-service.example.com/csc/v1",
        CredentialID: "my-signing-key",
        AuthToken:    "Bearer ey...",
    })
    
    // Use the signer with a document signing method
    doc.Sign(signer, cert).
        Reason("Approved").
        Write(output)
  10. Select a PDF signature format

    main

    The Format type determines the signature construction rules and compliance level (e.g., PAdES).

    Supported Formats:

    • DefaultFormat: Legacy /adbe.pkcs7.detached profile with embedded revocation info. Not PAdES compliant.
    • PAdES_B: ETSI signature construction rules (Baseline B-B). Does not embed revocation info.
    • PAdES_B_T: Extends PAdES_B by requiring a timestamp from a Time Stamp Authority (TSA). Requires a TSA URL via .Timestamp(url).
    • PAdES_B_LT: Extends PAdES_B_T by embedding validation material (OCSP/CRL) for long-term validation. (Note: Support status may vary based on library version).
    • PAdES_B_B: Alias for PAdES_B for source compatibility.

    Note: Formats like C2PA, JAdES_B_T, and PAdES_B_LTA are currently not supported.

  11. How the initials.Builder works

    main

    The AddInitials method returns an *initials.Builder. This builder is used to configure the initials.Config object that is stored within the Document.

    Key configuration aspects include:

    • Appearance: Derived from the Appearance object passed to AddInitials.
    • Position: Determines where on the page the initials are placed (e.g., TopLeft, TopRight, BottomLeft, BottomRight).
    • Exclusions: A list of page numbers to skip when adding initials.
    • Margins: MarginX and MarginY to offset the initials from the page edges.
  12. Run the DSS Validator using Docker Compose

    main

    The DSS Validator service can be deployed using Docker Compose. The service is built from Dockerfile.dss and exposes port 8080.

    A healthcheck is implemented to monitor the service availability by querying the REST validation endpoint. The service is considered healthy when curl -f http://localhost:8080/services/rest/validation/v2/validateSignature returns successfully.

    version: '3.8'
    
    services:
      dss-validator:
        build:
          context: .
          dockerfile: Dockerfile.dss
        ports:
          - "8080:8080"
        healthcheck:
          test: [ "CMD", "curl", "-f", "http://localhost:8080/services/rest/validation/v2/validateSignature" ]
          interval: 10s
          timeout: 5s
          retries: 5
          start_period: 30s