• adbe.pkcs7.detached, which is the default PDF document signature as used by Adobe and the most common type of signature
  • ETSI.PAdES/ETSI.CAdES.detached, which is ETSI/eIDAS compliant (needs special Certificates provided by eIDAS Trust Service Providers!)

Default PKCS#7 PDF signature

This is the default PDF Signature type, typically used with Certificates provided by an organization on the Adobe Approved Trusted List (AATL).

There are 2 types of signatures possible:

  • CERTIFICATION
    • Can only be applied once to a PDF document!
    • It acts like a seal, which typically is organization or department wide.
    • A blue bar will appear with name of the signer, the company and the CA that issued the Certificate
    • Allows to protect the document for further modifications at several levels
    • Optionally showing an image of the signature. Clickable to show more information
  • APPROVAL
    • Can be applied multiple times.
    • This is what typically is being used for people signing the document.
    • It is comparable to a user signing a paper based document.
    • The signature shows the name and additional information.
    • Optionally showing an image of the signature. Clickable to show more information

PKCS#7 configuration options

The below options are part of a configuration, but can typically also be provided on every invocation. This allows to use the same certificate/key for instance for signing by multiple people by changing the signerName and related properties.

kotlin
class Pkcs7SignatureFormParameters(
    /**
     * The signature mode, according to the PDF spec. Either needs to be APPROVAL or CERTIFICATION.
     *
     * - CERTIFICATION can only be applied once to a PDF document. It acts like a seal, which typically is organization or department wide.
     * A blue bar will appear with name of the signer, the company and the CA that issued the Certificate
     * - APPROVAL can be applied multiple times. This is what typically is being used for people signing the document. It is comparable to a user signing a paper based document.
     * The signature shows the name and additional information. Optionally showing an image of the signature. Clickable to show more information
     */
    val mode: PdfSignatureMode? = PdfSignatureMode.APPROVAL,

    /**
     * This attribute allows to explicitly specify the SignerName (name for the entity signing).
     * The person or authority signing the document.
     */
    val signerName: String,


    /** The signature creation reason  */
    val reason: String? = null,

    /** The contact info  */
    val contactInfo: String? = null,

    /** The signer's location  */
    val location: String? = null,

    /**
     * Defines the preserved space for a signature context. Only change if you know what you are doing
     *
     * Default : 9472 (default value in pdfbox)
     */
    val signatureSize: Int? = 9472,

    /**
     * This attribute allows to override the used Filter for a Signature.
     *
     * Default value is Adobe.PPKLite
     */
    val signatureFilter: String? = PdfSignatureFilter.ADOBE_PPKLITE.specName,

    /**
     * This attribute allows to override the used subFilter for a Signature.
     *
     * Default value is adbe.pkcs7.detached
     */
    val signatureSubFilter: String? = PdfSignatureSubFilter.ADBE_PKCS7_DETACHED.specName,

    /**
     * This attribute is used to create visible signature
     */
    val signatureImageParameters: SignatureImageParameters? = null,

    /**
     * This attribute allows to set permissions in case of a "certification signature". That allows to protect for
     * future change(s).
     */
    val permission: CertificationPermission? = null,

    /**
     * Password used to encrypt a PDF
     */
    val passwordProtection: String? = null
)

Example PKCS#7 flow

Below an example is provided where a local Signing Service and a Local Azure Keyvault Key Provider is being used to sign with a certificate on the AATL list, resulting in “blue-bar” signatures. The example key vault settings can be found above. The createSignature/verifySignature/getCert(s) methods would use the Azure Keyvault REST API, so we will be creating a digest first to ensure we are not sending the document to Azure Keyvault.

kotlin
// Gets the file and set the orig data object
val pdfDocInput = this::class.java.classLoader.getResource("example-unsigned.pdf")
val origData = OrigData(value = pdfDocInput.readBytes(), name = "example-unsigned.pdf")


val keyProvider = KeyProviderServiceFactory.createFromConfig(
    settings = providerSettings, // See above for examples
    azureKeyvaultClientConfig = keyvaultConfig // See example above
)
// The factory has returned an Azure Keyvault Key Provider at this point

// Create a local signature service, which uses kid/strings to denote the certificates to use
val signingService = KidSignatureService(keyProvider)

val kid = "example:3f98a9a740fb41b79e3679cce7a34ba6" // The kid is Azure Keyvault certificate specific and is <certificate Id>:<version>

val signatureConfiguration = SignatureConfiguration(
    signatureParameters = SignatureParameters(
        signaturePackaging = SignaturePackaging.ENVELOPED,
        digestAlgorithm = DigestAlg.SHA256,
        encryptionAlgorithm = CryptoAlg.RSA,
        signatureAlgorithm = SignatureAlg.RSA_SHA256,
        signatureLevelParameters = SignatureLevelParameters(
            signatureLevel = SignatureLevel.PKCS7_B, // This sets the mode to PDF PKCS#7 (Basic signature)
        ),
        signatureFormParameters = SignatureFormParameters(
            // PKCS#7 specific parameters
            pkcs7SignatureFormParameters = Pkcs7SignatureFormParameters(
                mode = PdfSignatureMode.APPROVAL,   // Use an approval signature
                signerName = "Example User",
                contactInfo = "example@sphereon.com",
                reason = "Example",
                location = "Amsterdam",
            )
        )
    )
)

// Locally extract the bytes to be signed from the PDF document
val signInput = signingService.determineSignInput(
    origData = origData,
    kid = kid,
    signMode = SignMode.DOCUMENT,
    signatureConfiguration = signatureConfiguration
)

// Locally create a hash/digest of the extracted bytes
val digestInput = signingService.digest(signInput)

// Calls Azure Keyvault using the hash/digest and the certificate associated with the kid value
val signature = signingService.createSignature(digestInput, kid)

// Locally combine the original document with the created signature
val signOutput = signingService.sign(origData, signature, signatureConfiguration)

ETSI eIDAS PAdES detached PDF signature

This is the eIDAS/ETSI compliant PDF Signature type, used with Certificates provided by eIDAS Trust service providers.

PAdES configuration options

The below options are part of a configuration, but can typically also be provided on every invocation. This allows to use the same certificate for instance for signing by multiple people by changing the signerName and related properties.

kotlin
class PadesSignatureFormParameters(
    /**
     * This attribute allows to explicitly specify the SignerName (name for the Signature).
     * The person or authority signing the document.
     */
    val signerName: String? = null,

    /** The signature creation reason  */
    val reason: String? = null,

    /** The contact info  */
    val contactInfo: String? = null,

    /** The signer's location  */
    val location: String? = null,

    /**
     * Defines the preserved space for a signature context
     *
     * Default : 9472 (default value in pdfbox)
     */
    val signatureSize: Int? = 9472,

    /**
     * This attribute allows to override the used Filter for a Signature.
     *
     * Default value is Adobe.PPKLite
     */
    val signatureFilter: String? = PdfSignatureFilter.ADOBE_PPKLITE.specName,

    /**
     * This attribute allows to override the used subFilter for a Signature.
     *
     * Default value is ETSI.CAdES.detached
     */
    val signatureSubFilter: String? = PdfSignatureSubFilter.ETSI_CADES_DETACHED.specName,


    /**
     * This attribute is used to create visible signature in PAdES form
     */
    val signatureImageParameters: SignatureImageParameters? = null,

    /**
     * This attribute allows to create a "certification signature". That allows to remove permission(s) in case of
     * future change(s).
     */
    val permission: CertificationPermission? = null,

    /**
     * Password used to encrypt a PDF
     */
    val passwordProtection: String? = null,

    /** Defines if the signature shall be created according to ETSI EN 319 122  */
    val en319122: Boolean? = true,

    /** Content Hints type  */
    val contentHintsType: String? = null,

    /** Content Hints description  */
    val contentHintsDescription: String? = null,

    /** Content identifier prefix  */
    val contentIdentifierPrefix: String? = null,

    /** Content identifier suffix  */
    val contentIdentifierSuffix: String? = null

)

Example PAdES flow

Below an example is provided where a local Signing Service and a Key Provider using a Hardware Security Module accessed using the PKCS#12 interface with a certificate provided by a Qualified Trust Service Provider.

kotlin
// Gets the file and set the orig data object
val pdfDocInput = this::class.java.classLoader.getResource("example-unsigned.pdf")
val origData = OrigData(value = pdfDocInput.readBytes(), name = "example-unsigned.pdf")


val keyProvider = KeyProviderServiceFactory.createFromConfig(
    settings = providerSettings, // See above for examples
)
// The factory has returned a Local Key Provider with PKCS#12 HSM support at this point

// Create a local signature service, which uses kid/strings to denote the certificates to use
val signingService = KidSignatureService(keyProvider)

val kid = "example" // The kid is HSM specific and is <certificate Id>

val signatureConfiguration = SignatureConfiguration(
    signatureParameters = SignatureParameters(
        signaturePackaging = SignaturePackaging.ENVELOPED,
        digestAlgorithm = DigestAlg.SHA256,
        encryptionAlgorithm = CryptoAlg.RSA,
        signatureAlgorithm = SignatureAlg.RSA_SHA256,
        signatureLevelParameters = SignatureLevelParameters(
            signatureLevel = SignatureLevel.PAdES_BASELINE_B, // This sets the mode to PDF PAdES (Basic signature)
        ),
        signatureFormParameters = SignatureFormParameters(
            // PAdES specific parameters
            padesSignatureFormParameters = PadesSignatureFormParameters(
                signerName = "Example User",
                contactInfo = "example@sphereon.com",
                location = "Amsterdam",
            )
        )
    )
)

// Locally extract the bytes to be signed from the PDF document
val signInput = signingService.determineSignInput(
    origData = origData,
    kid = kid,
    signMode = SignMode.DOCUMENT,
    signatureConfiguration = signatureConfiguration
)

// Locally create a hash/digest of the extracted bytes
val digestInput = signingService.digest(signInput)

// Calls the HSM using the hash/digest and the certificate associated with the kid value
val signature = signingService.createSignature(digestInput, kid)

// Locally combine the original document with the created signature
val signOutput = signingService.sign(origData, signature, signatureConfiguration)

Visual PKCS#7 and PAdES signatures

It is possible to create visual signatures. These signatures show an image of a ‘wet signature’ by providing an image file, or alternatively they are created from provided text. These visual signatures will show up in the document, and can be clicked upon to show more information.

Both PAdES and PKCS#7 type of PDF signatures have option to add visual signature options in their respective SignatureFormParameters

PKCS#7 Signature Form Parameters using an image

kotlin
/**
 * This attribute is used to create visible signature inside the Pkcs7FormParameters
 */
signatureImageParameters = SignatureImageParameters(
    image = ByteArray(),                // The company logo or "wet signature" image as byte array
    zoom = 150,                         // Scale the image to 150%, defaults to 100%
    imageScaling = ImageScaling.STRETCH,// Stretches the image in both directions

    signatureFieldParameters = SignatureFieldParameters(
        fieldId = "Signature 1",    // Signature field id/name
        page = 2,                   // Page number where the signature field is added (defaults to 1)
        originX = 10f,              // Coordinate X where to add the signature field (origin is top/left corner)
        originY = 50f,              // Coordinate Y where to add the signature field (origin is top/left corner)
        width = 100f,               // Signature field width
        height = 30f                // Signature field height
    )

)

PKCS#7 Signature Form Parameters using text

kotlin
/**
 * This attribute is used to create visible signature inside the Pkcs7FormParameters
 */
signatureImageParameters = SignatureImageParameters(
    signatureImageTextParameters = SignatureImageTextParameters(
        text = "John Doe, CEO",                 // This variable defines the text
        textWrapping = TextWrapping.FILL_BOX,   // Fills the box adjusting the font-size to match
        padding = 10,                           // padding in pixels, defaults to 5
        textColor = "RED",
        backgroundColor = "WHITE"
    ),
    signatureFieldParameters = SignatureFieldParameters(
        fieldId = "Signature 1",    // Signature field id/name
        page = 2,                   // Page number where the signature field is added (defaults to 1)
        originX = 10f,              // Coordinate X where to add the signature field (origin is top/left corner)
        originY = 50f,              // Coordinate Y where to add the signature field (origin is top/left corner)
        width = 100f,               // Signature field width
        height = 30f                // Signature field height
    )
)

Signature Image Parameters is the main class to configure Visual Signatures.

kotlin
class SignatureImageParameters(

    /**
     * This variable contains the image to use (company logo,...)
     */
    val image: ByteArray? = null,

    /**
     * This variable defines a `SignatureFieldParameters` like field positions and dimensions
     */
    val fieldParameters: SignatureFieldParameters? = null,

    /**
     * This variable defines a percent to zoom the image (100% means no scaling).
     * Note: This does not touch zooming of the text representation.
     */
    val zoom: Int = NO_SCALING,

    /**
     * This variable defines the color of the image
     */
    val backgroundColor: String? = null,

    /**
     * This variable defines the DPI of the image
     */
    val dpi: Int? = null,

    /**
     * Use rotation on the PDF page, where the visual signature will be
     */
    val rotation: VisualSignatureRotation? = null,

    /**
     * Horizontal alignment of the visual signature on the pdf page
     */

    val alignmentHorizontal: VisualSignatureAlignmentHorizontal? = VisualSignatureAlignmentHorizontal.NONE,

    /**
     * Vertical alignment of the visual signature on the pdf page
     */
    val alignmentVertical: VisualSignatureAlignmentVertical? = VisualSignatureAlignmentVertical.NONE,

    /**
     * Defines the image scaling behavior within a signature field with a fixed size
     *
     * DEFAULT : ImageScaling.STRETCH (stretches the image in both directions to fill the signature field)
     */
    val imageScaling: ImageScaling? = ImageScaling.STRETCH,

    /**
     * This variable is use to defines the text to generate on the image
     */
    val textParameters: SignatureImageTextParameters? = null
)

The Signature Field Parameters define the location and dimensions of the signature

kotlin
class SignatureFieldParameters(
    /** Signature field id/name (optional)  */
    val fieldId: String? = null,

    /** Page number where the signature field is added  */
    val page: Int = DEFAULT_FIRST_PAGE,

    /** Coordinate X where to add the signature field (origin is top/left corner)  */
    val originX: Float = 0f,

    /** Coordinate Y where to add the signature field (origin is top/left corner)  */
    val originY: Float = 0f,

    /** Signature field width  */
    val width: Float = 0f,

    /** Signature field height  */
    val height: Float = 0f
)

Signature Image Text parameters define text and the appearance to place in the visual signature

kotlin
class SignatureImageTextParameters(
    /**
     * This variable allows to add signer name on the image (by default, LEFT)
     */
    val signerTextPosition: SignerTextPosition = SignerTextPosition.LEFT,

    /**
     * This variable defines the image from text vertical alignment in connection
     * with the image<br></br>
     * <br></br>
     * It has effect when the [SignerPosition][SignerTextPosition] is
     * [LEFT][SignerTextPosition.LEFT] or [ RIGHT][SignerTextPosition.RIGHT]
     */
    val signerTextVerticalAlignment: SignerTextVerticalAlignment = SignerTextVerticalAlignment.MIDDLE,

    /**
     * This variable set the more line text horizontal alignment
     */
    val signerTextHorizontalAlignment: SignerTextHorizontalAlignment = SignerTextHorizontalAlignment.LEFT,

    /**
     * This variable defines the text to sign
     */
    val text: String? = null,

    /**
     * This variable defines the font to use
     * (default is PTSerifRegular)
     */
    val font: String? = null,

    /**
     * This variable defines how the given text should be wrapped within the signature field's box
     *
     * Default : TextWrapping.FONT_BASED - the text is computed based on the `Font` configuration
     */
    val textWrapping: TextWrapping = TextWrapping.FONT_BASED,

    /**
     * This variable defines a padding in pixels to bound text around
     * (default is 5)
     */
    val padding: Float = DEFAULT_PADDING,

    /**
     * This variable defines the text color to use
     * (default is BLACK)
     * (PAdES visual appearance: allow null as text color, preventing graphic operators)
     */
    val textColor: String? = DEFAULT_TEXT_COLOR,

    /**
     * This variable defines the background of a text bounding box
     */
    val backgroundColor: String? = DEFAULT_BACKGROUND_COLOR
)