tofi: Architecture of a PKI-Based Digital Document Signing Platform
Cryptographic signing algorithms, certificate validation pipelines, and architectural decisions behind tofi, a document signing platform built on PKCS#12 and X.509 standards
The digital signature of documents in regulated environments remains one of the most technically demanding challenges in applied cryptography. The process extends far beyond appending an image to a PDF: it requires extracting private keys from PKCS#12 containers, parsing X.509 certificate chains with jurisdiction-specific OID extensions, validating expiration windows across every certificate in the chain, and embedding a cryptographically bound signature into the PDF structure itself. Each of these operations carries its own failure surface, and a single miscalculation in any of them renders the entire signature legally void.
tofi addresses this challenge with engineering rigor. It is a document signing platform that implements the full PKI lifecycle, from certificate ingestion and validation through digital signature application to document verification, built on Laravel 10, OpenSSL, and TCPDF as its cryptographic and document-processing foundation. The platform serves over 2,000 monthly users who depend on its signing infrastructure for legally binding documents.
This document presents the cryptographic algorithms, certificate validation pipelines, and architectural decisions that sustain the platform, with the depth and precision that each of these choices demands.
Signing Architecture
The signing engine constitutes the most algorithmically dense component of the entire system. It orchestrates six discrete operations in strict sequence: PKCS#12 extraction, X.509 parsing, certificate chain validation, QR code generation with embedded metadata, signature application via TCPDF, and persistent storage on Cloudflare R2. A failure at any stage halts the pipeline and returns a precise diagnostic to the client, ensuring that no partially signed document ever reaches storage.
PKCS#12 Extraction and Private Key Isolation
The signing process begins when the client submits a PDF document alongside a .p12 certificate file and its corresponding password. The server invokes openssl_pkcs12_read to decrypt the PKCS#12 container, extracting the private key, the signing certificate, and any intermediate certificates embedded in the extracerts array. If the password is incorrect or the container is malformed, the operation terminates immediately with a diagnostic response:
$pkcs12 = file_get_contents($fullPath);
$certs = [];
if (!openssl_pkcs12_read($pkcs12, $certs, $certificate_password)) {
return response()->json(['error' => __('signature.incorrect_password')], 400);
}
$privateKey = $certs['pkey'] ?? null;
$certificate = $certs['cert'] ?? null;The extraction yields three artifacts: the private key used to generate the digital signature, the X.509 certificate that binds the signer's identity to that key, and optionally a chain of intermediate CA certificates that establish the trust path to a root authority.
X.509 Certificate Parsing with Jurisdiction-Specific OID Resolution
Certificate identity extraction is far more complex than reading the subject field of an X.509 certificate. Ecuadorian digital certificates, issued by authorities such as ANF AC and Security Data, encode signer identity in custom OID extensions rather than in the standard subject fields. The algorithm implements a three-tier fallback strategy to handle every certificate format encountered in production:
// Tier 1: Ecuador-specific OID extensions
foreach ($ext as $key => $value) {
$data = explode('.', $key);
$penultimo = $data[count($data) - 2];
$ultimo = $data[count($data) - 1];
if ($penultimo == '3' && $ultimo == '1') {
$identificacion = trim(preg_replace('/[\x00-\x1F\x7F]+/', '', $value));
}
if ($penultimo == '3' && $ultimo == '2') {
$nombre = trim(preg_replace('/[\x00-\x1F\x7F]+/', '', $value));
}
if ($penultimo == '3' && $ultimo == '3') {
$apellido = trim(preg_replace('/[\x00-\x1F\x7F]+/', '', $value));
}
}
// Tier 2: Standard X.509 subject fields
if ($nombre == 'Desconocido' && $apellido == 'Desconocido') {
if (isset($x509['subject']['GN'])) {
$nombre = trim($x509['subject']['GN']);
}
if (isset($x509['subject']['SN'])) {
$apellido = trim($x509['subject']['SN']);
}
}
// Tier 3: Common Name decomposition
if ($nombre === 'Desconocido' && $apellido === 'Desconocido') {
if (isset($x509['subject']['CN'])) {
$cnValue = $x509['subject']['CN'];
$nombreApellido = explode(' ', $cnValue, 2);
if (count($nombreApellido) == 2) {
$nombre = trim($nombreApellido[0]);
$apellido = trim($nombreApellido[1]);
}
}
}The OID pattern .3.1, .3.2, .3.3 maps respectively to the signer's identification number, given name, and surname, a convention specific to Ecuadorian certificate authorities. The control character sanitization via regex ensures that non-printable bytes embedded by certain CA implementations do not corrupt the extracted identity data. This multi-tier approach guarantees that the system resolves signer identity regardless of the issuing authority's encoding choices.
Certificate Chain Validation and Temporal Boundaries
Certificate validation extends beyond the signing certificate itself. A PKCS#12 container may include intermediate certificates in its extracerts array, and the system must determine the effective validity window across the entire chain. The algorithm iterates through each intermediate certificate, identifies those containing jurisdiction-specific OID markers, and computes the minimum emission date and maximum expiration date across all relevant certificates:
$minEmissionDate = $certEmissionDate;
$maxExpirationDate = $certExpirationDate;
if (isset($cert_info['extracerts']) && !empty($cert_info['extracerts'])) {
foreach ($cert_info['extracerts'] as $extracert) {
$extX509 = openssl_x509_parse($extracert);
$ext = $extX509['extensions'];
$foundIdentificador = false;
foreach ($ext as $key => $value) {
$data = explode('.', $key);
if (count($data) > 1 && $data[count($data) - 2] == '3'
&& $data[count($data) - 1] == '1') {
$foundIdentificador = true;
break;
}
}
if ($foundIdentificador) {
$extracertEmissionDate = $this->convertDate($extX509['validFrom']);
$extracertExpirationDate = $this->convertDate($extX509['validTo']);
$minEmissionDate = min($minEmissionDate, $extracertEmissionDate);
$maxExpirationDate = max($maxExpirationDate, $extracertExpirationDate);
}
}
}The filtering condition, which only considers certificates bearing the .3.1 OID marker, prevents the algorithm from incorporating unrelated CA certificates into the validity calculation. This distinction is critical: a root CA certificate with a 20-year validity window should not inflate the effective expiration of a signer's personal certificate.
Digital Signature Embedding and Visual Representation
Once the certificate clears validation, the system generates a composite visual signature comprising a QR code and textual identification. The QR code encodes a JSON payload with the signer's identity, timestamp, and positioning metadata, providing a machine-readable verification vector. The signature is then cryptographically embedded into the PDF structure via TCPDF:
$pdf->setSignature($certificate, $privateKey, '', '', 2, $info);The signature mode parameter 2 activates visible signature rendering, binding the cryptographic proof to a specific visual region on the selected page. The resulting signature block occupies approximately 43mm in width, combining an 8mm QR code with a 35mm textual area displaying the signer's name and identification number.
Certificate Validation Engine
The certificate validation module operates as an independent verification service, enabling users to inspect the contents and validity status of any P12 certificate without initiating a signing operation. This separation of concerns allows auditors and compliance officers to verify certificate metadata before authorizing its use in production signing workflows.
The validation pipeline produces a comprehensive report containing the issuing authority, the signer's identification number, full name, institutional affiliation, role designation, emission and expiration dates, and current validity status. The date handling deserializes OpenSSL's UTC format manually, parsing the YYMMDDHHMMSSZ string into its temporal components:
private function convertDate(string $fechaUTC): string
{
$year = '20' . substr($fechaUTC, 0, 2);
$month = substr($fechaUTC, 2, 2);
$day = substr($fechaUTC, 4, 2);
$hour = substr($fechaUTC, 6, 2);
$minute = substr($fechaUTC, 8, 2);
$second = substr($fechaUTC, 10, 2);
return "$year-$month-$day $hour:$minute:$second";
}The decision to implement manual parsing rather than relying on PHP's DateTime constructor reflects the inconsistency of OpenSSL's date output across certificate authorities. Certain CAs emit dates with trailing Z markers while others omit them, and the validFrom and validTo fields do not guarantee a format that strtotime can reliably interpret.
Document Verification
The document verification module addresses the inverse challenge: given a signed PDF, extracting and validating the signatures it contains. The system parses the embedded text layer of the PDF and applies a cascade of regular expressions calibrated to the signature format that the signing engine produces:
$patterns = [
'/Firmado digitalmente por:?\s*([^:\n]+)[\s\n]*(?:C\.I\.|CI|Cédula)\.?\s*(?:No\.?)?\s*(\d+)/i',
'/Firmado por:?\s*([^:\n]+)[\s\n]*(?:C\.I\.|CI|Cédula)\.?\s*(?:No\.?)?\s*(\d+)/i',
'/([^:\n]+)[\s\n]*(?:C\.I\.|CI|Cédula)\.?\s*(?:No\.?)?\s*(\d+)/i'
];Each pattern targets a progressively broader matching window, with the most specific pattern applied first. The extracted identification numbers undergo format validation against the Ecuadorian cédula standard, which mandates exactly ten digits. This constraint filters spurious matches that might arise from unrelated numeric sequences in the document body.
The verification response aggregates all detected signatures with their associated metadata: signer name, identification number, issuing certification entity, and signing timestamp.
Service Layer Architecture
The platform enforces a deliberate separation between HTTP controllers and business logic through a dedicated service layer. This architectural boundary ensures that cryptographic operations, storage interactions, and data transformations remain decoupled from request handling.
The CertificateService encapsulates all X.509 certificate operations: identity extraction with multi-tier OID resolution, chain-aware date computation, and UTC date conversion. Every controller that interacts with certificates delegates to this service, ensuring consistent parsing behavior across the signing, validation, and verification modules.
Authentication and Webhook Security
The platform delegates identity management to Clerk, synchronizing user records through a webhook pipeline secured with HMAC-SHA256 signature verification. Every incoming webhook payload is validated against the Svix signature scheme before any mutation occurs:
private function verifyWebhook(Request $request, string $secret): bool
{
$svixId = $request->header('svix-id');
$svixTimestamp = $request->header('svix-timestamp');
$svixSignature = $request->header('svix-signature');
$payload = $request->getContent();
$signedContent = "{$svixId}.{$svixTimestamp}.{$payload}";
$secretBytes = base64_decode(substr($secret, 6));
$expectedSignature = base64_encode(
hash_hmac('sha256', $signedContent, $secretBytes, true)
);
$signatures = explode(' ', $svixSignature);
foreach ($signatures as $sig) {
$parts = explode(',', $sig);
if (count($parts) === 2 && $parts[1] === $expectedSignature) {
return true;
}
}
return false;
}The whsec_ prefix stripping on the secret key, the dot-delimited content concatenation, and the multi-signature iteration reflect the Svix protocol specification that Clerk employs for webhook delivery. This verification ensures that user creation, update, and deletion events originate exclusively from Clerk's infrastructure.
Storage and Persistence Strategy
Signed documents persist on Cloudflare R2 as immutable objects, leveraging its S3-compatible API for uploads and its global CDN for retrieval. The storage layer implements a graceful degradation pattern: if the R2 upload fails, the signed PDF falls back to Laravel's local filesystem, ensuring that no signing operation is lost due to transient cloud connectivity issues.
Technology Stack
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Laravel 10 / PHP 8.1+ | HTTP routing, middleware, ORM, service container |
| Cryptography | OpenSSL | PKCS#12 extraction, X.509 parsing, digital signature generation |
| PDF Engine | TCPDF + FPDI | PDF manipulation, signature embedding, page import |
| PDF Analysis | smalot/pdfparser, spatie/pdf-to-text | Text extraction for signature verification |
| QR Generation | endroid/qr-code | Machine-readable signature metadata encoding |
| Object Storage | Cloudflare R2 via AWS SDK | Immutable signed document persistence |
| Authentication | Clerk + Svix webhooks | Identity management with HMAC-verified synchronization |
| Error Tracking | Sentry | Production error monitoring and diagnostics |
| Cache | Redis via Predis | Session management and query caching |
API Surface
The platform exposes a RESTful API organized around four functional domains:
- POST
/api/pdf/signature— Submit a PDF with a P12 certificate and password to produce a digitally signed document - GET
/pdf/{fileName}— Retrieve an uploaded PDF for preview before signing
- GET
/api/documentos-firmados/— List all signed documents for the authenticated user - GET
/api/documentos-firmados/{filename}/download— Download a specific signed document - DELETE
/api/documentos-firmados/{filename}— Remove a signed document from storage - DELETE
/api/firmados/eliminar-todos— Bulk deletion of all signed documents
- POST
/api/share/link— Generate a password-protected download link with configurable expiration - GET
/api/share/download/{token}— Retrieve a shared file via its UUID token - POST
/api/share/download/{token}/validate— Authenticate against a protected share link - GET
/api/share/check/{token}— Inspect the protection status of a share link
- POST
/verify-certificate— Validate a P12 certificate and retrieve its metadata without authentication
Security Model
The security architecture operates across three concentric layers. At the external boundary, Clerk manages identity federation while Svix webhook signatures prevent unauthorized mutation of user records. The middleware layer enforces JWT issuer validation, HMAC-SHA256 verification on incoming webhooks, and strict file type constraints that accept only PDF documents within a 10MB limit. The internal layer governs cryptographic operations: certificate passwords are verified through OpenSSL before any key extraction occurs, certificate chain expiration is assessed against the current timestamp, share link passwords are hashed with bcrypt, and download tokens are generated as UUIDs to prevent enumeration.
Community Impact
tofi is not an open-source project, a deliberate decision driven by the regulatory requirements that govern digital signature platforms in Ecuador. The legal framework surrounding electronic signatures, certificate authority compliance, and document validity imposes constraints that preclude unrestricted distribution of the signing engine. This is not a limitation born of reluctance but of responsibility: the integrity of a digital signature platform depends on controlled deployment environments where certificate handling, key storage, and audit trails operate under strictly defined conditions.
The value that tofi contributes to its community of over 2,000 monthly users resides in the democratization of a capability that was previously accessible only through expensive proprietary solutions or cumbersome desktop applications. Professionals, legal practitioners, and institutions now access production-grade digital signing through a modern web interface, eliminating the friction that historically accompanied PKI-based document workflows.
The platform is available at tofi.pro, where the signing infrastructure, certificate validation, and document verification capabilities documented in this article serve real users in production every day. The architectural decisions presented here, from the multi-tier OID resolution to the chain-aware certificate validation, represent engineering solutions to problems that every team working with digital signatures in regulated environments will inevitably confront.