Skip to content

Server Integration ​

Learn how to integrate server-backed PKI signing with @signkit/core for legally binding signatures.

Overview ​

The library handles client-side form design and signing. For court-admissible, legally binding signatures, integrate with a server that provides:

  • PKI Certificates: Proof of signer identity
  • Timestamps: Proof of when signed
  • Digital Signatures: Cryptographic proof (PAdES, CMS, etc.)

The typical flow:

1. Client signs locally (draw signature, fill fields)
   ↓
2. Client sends manifest to server
   ↓
3. Server verifies manifest, applies PKI signature
   ↓
4. Server returns PDF with embedded certificate
   ↓
5. PDF is legally binding ✓

Client-Side: Capture Signing Data ​

vue
<template>
  <Signer
    :pdf-src="pdfUrl"
    :template="template"
    mode="integrity"
    @finalized="sendToServer"
  />
</template>

<script setup lang="ts">
async function sendToServer(payload) {
  const {
    values,          // Filled field values
    signedPdf,       // User-signed PDF (Blob)
    manifest,        // Manifest with hashes
  } = payload

  // Send to server
  const formData = new FormData()
  formData.append('signedPdf', signedPdf)
  formData.append('manifest', JSON.stringify(manifest))
  formData.append('signer', JSON.stringify(manifest.signer))

  const response = await fetch('/api/finalize-with-pki', {
    method: 'POST',
    body: formData,
  })

  if (!response.ok) {
    throw new Error('Server signing failed')
  }

  const { pdfWithCert } = await response.json()
  // Download legally binding PDF
  downloadPdf(pdfWithCert, 'signed-legally-binding.pdf')
}
</script>

Server-Side: Apply PKI Signature ​

Here's a Node.js example with a PKI service (e.g., AWS IoT Jobs, Azure Key Vault, DigiCert):

typescript
import express from 'express'
import pdfLib from 'pdf-lib'
import crypto from 'crypto'

const app = express()

app.post('/api/finalize-with-pki', async (req, res) => {
  try {
    // 1. Parse request
    const { signedPdf, manifest, signer } = req.body
    const pdfBytes = Buffer.from(signedPdf, 'base64')

    // 2. Verify manifest integrity (optional but recommended)
    const manifestHash = crypto
      .createHash('sha256')
      .update(JSON.stringify(manifest))
      .digest('hex')

    if (manifestHash !== manifest.templateHash) {
      return res.status(400).json({ error: 'Manifest tampered with' })
    }

    // 3. Load PDF with pdf-lib
    const pdfDoc = await pdfLib.PDFDocument.load(pdfBytes)

    // 4. Call PKI service (example: AWS Signer, DigiCert, Entrust, etc.)
    const { signature, timestamp, certificate } = await signWithPKI(
      pdfBytes,
      signer
    )

    // 5. Embed PKI signature in PDF (varies by format)
    // For simplicity, here we add metadata; real implementations use CMS/PAdES
    const page = pdfDoc.getPage(0)
    page.drawText(`Signed: ${signer.name} at ${timestamp}`, {
      x: 50,
      y: 50,
      size: 12,
    })

    // 6. Save and return
    const pdfWithCert = await pdfDoc.save()
    res.json({
      pdfWithCert: Buffer.from(pdfWithCert).toString('base64'),
      signature,
      timestamp,
      certificate,
    })
  } catch (error) {
    console.error(error)
    res.status(500).json({ error: error.message })
  }
})

async function signWithPKI(pdfBytes, signer) {
  // Integrate with your PKI provider
  // Examples:
  // - AWS Signer (KMS)
  // - DigiCert (API)
  // - Entrust
  // - Azure Key Vault
  // - OpenDSU/OpenESSIF

  const timestamp = new Date().toISOString()
  const signature = crypto.sign(
    'sha256',
    pdfBytes,
    {
      key: process.env.PRIVATE_KEY,
    }
  )

  return {
    signature: signature.toString('base64'),
    timestamp,
    certificate: process.env.CERTIFICATE,
  }
}

app.listen(3000)

PKI Service Integration Examples ​

AWS Signer (KMS) ​

typescript
import { SignerClient, SignCommand } from '@aws-sdk/client-signer'

async function signWithAWS(pdfBytes, signer) {
  const client = new SignerClient()
  const command = new SignCommand({
    profileName: 'prod-signer',
    signatureAlgorithm: 'ECDSA',
    messageFormat: 'RAW',
    message: pdfBytes,
  })
  const response = await client.send(command)
  return {
    signature: response.signature,
    timestamp: response.signingTime,
    certificate: response.certificate,
  }
}

DigiCert API ​

typescript
async function signWithDigiCert(pdfBytes, signer) {
  const response = await fetch('https://api.digicert.com/v2/sign', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.DIGICERT_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      pdf_base64: Buffer.from(pdfBytes).toString('base64'),
      signer_name: signer.name,
      signer_email: signer.email,
    }),
  })
  const data = await response.json()
  return {
    signature: data.signature,
    timestamp: data.timestamp,
    certificate: data.certificate_chain,
  }
}

Azure Key Vault ​

typescript
import { CryptographyClient, KryptographyKeyClient } from '@azure/keyvault-keys'

async function signWithAzureKV(pdfBytes, signer) {
  const client = new CryptographyClient(vaultUrl, credential)
  const signResult = await client.sign('RS256', pdfBytes)
  return {
    signature: signResult.result.toString('base64'),
    timestamp: new Date().toISOString(),
    certificate: process.env.AZURE_CERT,
  }
}

PAdES Embedding (Advanced) ​

For advanced use cases, embed PKI signatures in PAdES format (PDF Advanced Electronic Signatures):

typescript
import { PDFDocument } from 'pdf-lib'
import { CMS } from 'pkijs'

async function embedPADeS(pdfBytes, signature, certificate) {
  // PAdES requires specific PDF structure + CMS signature container
  // Libraries: PDFtk, iText (Java), pdfbox (Java), OpenPGP.js

  // Simplified example:
  const pdfDoc = await PDFDocument.load(pdfBytes)

  // Create CMS container with signature
  const cms = new CMS.SignedData({
    version: 3,
    digestAlgorithms: [{ algorithm: '2.16.840.1.101.3.4.2.1' }], // SHA-256
    contentInfo: { contentType: '1.2.840.113549.1.7.1' },
    certificates: [certificate],
    signerInfos: [
      {
        version: 3,
        sid: { issuerAndSerialNumber: { issuer: certificate.issuer } },
        digestAlgorithm: { algorithm: '2.16.840.1.101.3.4.2.1' },
        signatureAlgorithm: { algorithm: '1.2.840.113549.1.1.11' }, // sha256WithRSAEncryption
        signature: signature,
      },
    ],
  })

  // Embed in PDF signature field
  // (requires specialized PDF signing library)

  return pdfDoc
}

For production PAdES, consider using:

  • iText (Java/C#/.NET) - Full PAdES support
  • PDFtk - Command-line PAdES
  • OpenDSU - Open-source PKI/PAdES
  • LTV (Long-Term Validation) - Add timestamps and OCSP responses

Verification Flow ​

Users can verify a PKI-signed PDF with command-line tools or dedicated libraries:

bash
# Verify signature on Linux/Mac
openssl cms -verify -in signed.pdf -inform DER -nointern -nosigs -certfile cert.pem

Or in JavaScript:

typescript
import { verifyPDF } from 'pdf-signing-library'

const result = await verifyPDF('signed-legally-binding.pdf')
console.log(result)
// {
//   valid: true,
//   signer: 'Alice Johnson',
//   timestamp: '2024-01-15T10:00:00Z',
//   certificate: { subject: '...', issuer: '...' },
// }

Compliance & Regulations ​

Different jurisdictions require different signing formats:

RegionLawFormatLibrary
EUeIDASPAdES, CMSiText, OpenDSU
USAESIGN Act, UETACMS, XML-DSigiText, JSXSign
CanadaPIPEDAPAdES, CMSiText
GloballyISO 32000PDF/A-3 with XAdESPDFtk, iText

Always consult legal counsel for your jurisdiction's requirements.

Example: Complete End-to-End Flow ​

Client-side:

vue
<Signer
  mode="integrity"
  @finalized="(p) => sendToServer(p)"
/>

Server-side:

typescript
app.post('/api/finalize-with-pki', async (req, res) => {
  const manifest = req.body.manifest
  
  // Verify manifest
  if (!verifyManifestHash(manifest)) {
    return res.status(400).json({ error: 'Integrity check failed' })
  }

  // Sign with PKI
  const { pdfWithPKI } = await applyPKISignature(manifest)
  
  // Return to client
  res.json({ pdfWithPKI })
})

Result: Client has a locally signed PDF, server applies PKI, client downloads legally binding version.

Tips ​

  • ✅ Always verify manifest integrity before applying PKI
  • ✅ Use HTTPS for all communication
  • ✅ Log all signing events for audit trails
  • ✅ Include timestamps from a trusted authority
  • ✅ Test with your jurisdiction's requirements
  • ❌ Don't claim security without proper PKI integration
  • ❌ Don't store private keys in your client code

Released under the Apache License 2.0.