Skip to main content
Digital signatures are essential for Chilean electronic tax documents. LibreDTE Core handles both the electronic stamp (Timbre Electrónico/TED) and the XML digital signature using your digital certificate.

Overview

LibreDTE Core implements two types of signatures:
  1. Timbre Electrónico (TED): A stamp using the private key from your CAF (Código de Autorización de Folios)
  2. XML Digital Signature: A standard XML signature using your digital certificate (firma electrónica)

Digital Certificate Setup

Digital certificates in Chile are typically provided as .pfx or .p12 files. You need both the certificate data and its password.
$certificate = [
    'data' => file_get_contents('/path/to/certificate.pfx'),
    'password' => 'your_certificate_password',
];

// Use with bill() method
$bag = $documentComponent->bill(
    data: $documentData,
    caf: $caf,
    certificate: $certificate
);

Signature Process

When you provide a certificate to the bill() method, LibreDTE automatically signs the document:
1

Timestamp Addition

A timestamp (TmstFirma) is added to the document. You can provide a custom timestamp or use the current time.
$bag = $documentComponent->bill(
    data: $documentData,
    caf: $caf,
    certificate: $certificate,
    options: [
        'builder' => [
            'timestamp' => '2025-01-15T14:30:00',
        ],
    ]
);
2

Certificate Validation

The certificate validity is checked against the timestamp. The certificate must be active at the time of signing.
// AbstractBuilderStrategy.php:201
if (!$certificate->isActive($timestamp)) {
    throw new BuilderException(
        'El certificado digital no está vigente en el tiempo especificado'
    );
}
3

XML Signature

The document is signed using the XML-DSig standard with the certificate’s private key.
// AbstractBuilderStrategy.php:218-222
$xmlSigned = $this->signatureService->signXml(
    $xmlDocument,
    $certificate,
    $bag->getId()
);

Electronic Stamp (TED)

The TED (Timbre Electrónico) is created using the CAF’s private key. This happens automatically when you provide a CAF:
// The stamping process (AbstractBuilderStrategy.php:123-185)

// 1. Verify folio is in CAF range
if (!$caf->enRango($document->getFolio())) {
    throw new BuilderException('Folio fuera de rango');
}

// 2. Verify CAF is valid at timestamp
if (!$caf->vigente($timestamp)) {
    throw new BuilderException('CAF vencido');
}

// 3. Generate TED data structure
$tedData = $document->getPlantillaTED();
$tedData['TED']['DD']['CAF'] = $cafArray['AUTORIZACION']['CAF'];
$tedData['TED']['DD']['TSTED'] = $timestamp;

// 4. Sign with CAF private key using SHA1
$timbre = $this->signatureService->sign(
    $ddToStamp,
    $caf->getPrivateKey(),
    OPENSSL_ALGO_SHA1
);

// 5. Add stamp to document
$tedData['TED']['FRMT']['@value'] = $timbre;
The TED uses SHA1 algorithm as specified by SII, while the XML signature typically uses SHA256 or stronger algorithms.

Validating Signatures

LibreDTE Core provides methods to validate signatures on existing documents:
$validatorWorker = $documentComponent->getValidatorWorker();

try {
    // Validate from DocumentBag
    $validatorWorker->validateSignature($bag);
    echo "Signature is valid!";
} catch (\Exception $e) {
    echo "Invalid signature: " . $e->getMessage();
}

Certificate Information

Retrieve information from a loaded certificate:
use Derafu\Certificate\Service\CertificateLoader;

$loader = new CertificateLoader();
$certificate = $loader->load('/path/to/certificate.pfx', 'password');

// Get certificate details
$commonName = $certificate->getName();      // Certificate owner
$issuer = $certificate->getIssuer();        // Certificate authority
$serialNumber = $certificate->getSerial();  // Serial number
$validFrom = $certificate->getFrom();       // Start validity date
$validTo = $certificate->getTo();           // End validity date

// Check if certificate is currently valid
$isActive = $certificate->isActive();

// Check if valid at specific time
$wasActiveYesterday = $certificate->isActive('2025-01-14T10:00:00');

echo "Certificate: {$commonName}\n";
echo "Valid from: {$validFrom} to {$validTo}\n";
echo "Currently active: " . ($isActive ? 'Yes' : 'No');

Signing Without CAF

You can sign a document with only a certificate (no electronic stamp):
// Create document without CAF
$bag = $documentComponent->bill(
    data: $documentData,
    caf: null,  // No CAF = no electronic stamp
    certificate: $certificate  // Only digital signature
);

// The document will be signed but won't have a TED stamp
$document = $bag->getDocument();
Documents without a TED stamp cannot be sent to SII. The CAF is required for all documents that need to be reported to the tax authority.

Signing Existing Documents

You can add a signature to a document that was created without one:
// Load existing XML
$xmlDocument = new \Derafu\Xml\XmlDocument();
$xmlDocument->loadXml($existingXml);

// Create bag from XML
$documentBagManager = $documentComponent->getDocumentBagManagerWorker();
$bag = $documentBagManager->create($xmlDocument, normalizeAll: false);

// Add certificate to bag
$bag->setCertificate($certificate);

// Sign
$builderWorker = $documentComponent->getBuilderWorker();
$strategy = $builderWorker->getStrategy($bag->getAlias());

// Use protected sign method via builder
$builderWorker->build($bag);

$signedDocument = $bag->getDocument();

Certificate Validation Options

Control certificate validation behavior:
$options = [
    'builder' => [
        // Use specific timestamp for validation
        'timestamp' => '2025-01-15T10:00:00',
    ],
];

$bag = $documentComponent->bill(
    data: $documentData,
    caf: $caf,
    certificate: $certificate,
    options: $options
);

Signature Verification with SII

For production documents, verify signatures with SII’s web service:
$integrationComponent = $app->getPackage('billing')
    ->getComponent('integration');

$siiWorker = $integrationComponent->getSiiLazyWorker();

// Create SII request
$request = new \libredte\lib\Core\Package\Billing\Component\Integration\Support\SiiRequest(
    certificate: $certificate,
    options: [
        'ambiente' => \libredte\lib\Core\Package\Billing\Component\Integration\Enum\SiiAmbiente::CERTIFICACION,
    ]
);

// Validate signature with SII
$response = $siiWorker->validateDocumentSignature(
    request: $request,
    company: '12345678-5',
    document: 33,  // Document type
    number: 150,   // Folio
    date: '2025-01-15',
    total: 500000,
    recipient: '87654321-9',
    signature: $document->getTED()  // Electronic stamp
);

if ($response->isValid()) {
    echo "Signature verified by SII!";
} else {
    echo "SII rejected signature: " . $response->getMessage();
}
Always test signature validation in SII’s certification environment before using production mode.

Common Issues

Ensure your certificate is valid for the timestamp you’re using. Check certificate validity dates:
if (!$certificate->isActive($timestamp)) {
    echo "Certificate not valid at {$timestamp}";
    echo "Valid from: {$certificate->getFrom()}";
    echo "Valid to: {$certificate->getTo()}";
}
If you get errors loading the certificate, verify the password is correct:
try {
    $certificate = $loader->load($path, $password);
} catch (\Exception $e) {
    echo "Failed to load certificate. Check password.";
}
Common causes:
  • XML was modified after signing
  • Certificate doesn’t match the signature
  • Incorrect signature algorithm
Use the validator to get detailed error messages:
try {
    $validatorWorker->validateSignature($xmlDocument);
} catch (\Derafu\Signature\Exception\SignatureException $e) {
    echo "Signature error: " . $e->getMessage();
}

Next Steps

Document Validation

Validate documents against SII schemas and business rules

SII Integration

Send signed documents to SII and verify their status

Build docs developers (and LLMs) love