marzsv/sv-dte-signer

PHP SDK for signing Documentos Tributarios Electrónicos (DTE) for El Salvador using JWS RS512

v1.1.0 2025-08-10 23:48 UTC

This package is auto-updated.

Last update: 2025-08-10 23:51:58 UTC


README

PHP Version Tests License

A PHP SDK for signing Documentos Tributarios Electrónicos (DTE) for El Salvador using JWS RS512 digital signatures.

Project Status

Production Ready - All core features implemented and tested ✅ MH Certificate Support - Full support for Ministerio de Hacienda certificate format ✅ Standards Compliant - Follows El Salvador DTE specifications ✅ Working Examples - Ready-to-run examples with test certificates ✅ Test Coverage - 11 tests, 23 assertions, 100% pass rate 🔒 Security - Secure memory handling and input validation

Features

  • Local signing: No external servers or JVM required
  • JWS RS512: Compliant with El Salvador DTE specifications
  • MH Certificate Support: Full support for Ministerio de Hacienda XML format
  • Standards Compliant: Follows El Salvador DTE specifications
  • Flexible input: Accept JSON directly or from files
  • Certificate validation: XML certificate parsing and validation
  • Error handling: Standardized error codes (COD_803, COD_812, etc.)
  • Security: Secure memory handling and input validation
  • Performance: <500ms signing time for typical DTEs
  • Modern PHP: 8.1+ with full type safety and PSR-12 compliance
  • Easy testing: Includes mock certificate generator for development

Quick Test Setup

For Testing and Development

  1. Clone and install:

    git clone https://github.com/marzsv/sv-dte-signer.git
    cd sv-dte-signer
    composer install
  2. Generate test certificates:

    php examples/mock_certificate_generator.php
  3. Run a quick test:

    php examples/basic_usage.php
  4. Run all tests:

    composer test

For Production Use

Install via Composer:

composer require marzsv/sv-dte-signer

Requirements

  • PHP 8.1 or higher
  • OpenSSL extension
  • libxml extension

Quick Start

Basic Usage

<?php

require_once 'vendor/autoload.php';

use Marzsv\DteSigner\DteSigner;

// Initialize the signer
$signer = new DteSigner();

// Prepare your DTE signing request
$request = [
    'nit' => '12345678901234',
    'passwordPri' => 'your_certificate_password',
    'dteJson' => [
        'identificacion' => [
            'version' => 1,
            'ambiente' => '00',
            'tipoDte' => '01',
            'numeroControl' => 'DTE-01-00000001-000000000000001',
            'codigoGeneracion' => 'A1B2C3D4-E5F6-7890-1234-567890ABCDEF',
            'fecEmi' => '2025-07-20',
            'horEmi' => '10:30:00'
        ],
        'emisor' => [
            'nit' => '12345678901234',
            'nombre' => 'EMPRESA EJEMPLO S.A. DE C.V.'
        ],
        'receptor' => [
            'nit' => '98765432109876',
            'nombre' => 'CLIENTE EJEMPLO S.A. DE C.V.'
        ],
        'resumen' => [
            'totalPagar' => 113.00
        ]
    ]
];

// Sign the DTE
$response = $signer->sign($request);

if ($response['success']) {
    echo "DTE signed successfully!\\n";
    echo "Signed JWS: " . $response['data'] . "\\n";
} else {
    echo "Error: " . $response['message'] . "\\n";
    echo "Code: " . $response['errorCode'] . "\\n";
}

Using JSON Files

<?php

use Marzsv\DteSigner\DteSigner;

$signer = new DteSigner();

// Sign from a JSON file
$response = $signer->sign('/path/to/dte_request.json');

if ($response['success']) {
    echo "DTE signed successfully from file!\\n";
}

Custom Certificate Directory

<?php

use Marzsv\DteSigner\DteSigner;

// Use a custom certificate directory
$signer = new DteSigner('/path/to/certificates');
$response = $signer->sign($request);

Certificate Setup

  1. Certificate Format: Certificates must be XML files with the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<certificate>
    <activo>true</activo>
    <verificado>true</verificado>
    <passwordHash>sha512_hash_of_password</passwordHash>
    <privateKey><![CDATA[-----BEGIN PRIVATE KEY-----...]]></privateKey>
</certificate>
  1. File Naming: Certificate files must be named {NIT}.crt (e.g., 12345678901234.crt)

  2. Directory: Place certificates in the certificates/ directory or specify a custom path

API Reference

DteSigner Class

Constructor

public function __construct(string $certificateDirectory = 'certificates')

Parameters:

  • $certificateDirectory: Path to the directory containing certificate files

sign()

public function sign(array|string $input): array

Parameters:

  • $input: Either an associative array with signing data or a file path to JSON

Returns:

  • Success response: {'success': true, 'message': '...', 'data': 'signed_jws'}
  • Error response: {'success': false, 'message': '...', 'errorCode': 'COD_XXX', 'errors': [...]}

Request Format

[
    'nit' => 'string(14)',           // Required: 14-digit NIT
    'passwordPri' => 'string(8-100)', // Required: Certificate password
    'dteJson' => array,              // Required: DTE document data
    'passwordPub' => 'string',       // Optional: Public key password
    'nombreDocumento' => 'string'    // Optional: Document name
]

Error Codes

Code Description
COD_803 Validation error
COD_812 Certificate not found
COD_813 Invalid certificate
COD_814 Password mismatch
COD_815 Signing error
COD_816 JSON encoding error
COD_500 Unexpected error

Example Response

Successful Signing Response

{
    "success": true,
    "message": "DTE signed successfully",
    "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJkdGUiOiJ7XG4gICAgXCJpZGVudGlmaWNhY2lvblwiOiB7XG4gICAgICAgIFwidmVyc2lvblwiOiAxLFxuICAgICAgICBcImFtYmllbnRlXCI6IFwiMDBcIixcbiAgICAgICAgXCJ0aXBvRHRlXCI6IFwiMDFcIixcbiAgICAgICAgXCJudW1lcm9Db250cm9sXCI6IFwiRFRFLTAxLTAwMDAwMDAxLTAwMDAwMDAwMDAwMDAwMVwiLFxuICAgICAgICBcImNvZGlnb0dlbmVyYWNpb25cIjogXCJBMUIyQzNENC1FNUY2LTc4OTAtMTIzNC01Njc4OTBBQkNERUZcIixcbiAgICAgICAgXCJmZWNFbWlcIjogXCIyMDI1LTA3LTIwXCIsXG4gICAgICAgIFwiaG9yRW1pXCI6IFwiMTA6MzA6MDBcIixcbiAgICAgICAgXCJ0aXBvTW9uZWRhXCI6IFwiVVNEXCJcbiAgICB9LFxuICAgIFwiZW1pc29yXCI6IHtcbiAgICAgICAgXCJuaXRcIjogXCIxMjM0NTY3ODkwMTIzNFwiLFxuICAgICAgICBcIm5vbWJyZVwiOiBcIkVNUFJFU0EgREUgRUpFTVBMTyBTLkEuIERFIEMuVi5cIixcbiAgICAgICAgXCJub21icmVDb21lcmNpYWxcIjogXCJFbXByZXNhIEVqZW1wbG9cIlxuICAgIH0sXG4gICAgXCJyZWNlcHRvclwiOiB7XG4gICAgICAgIFwibml0XCI6IFwiOTg3NjU0MzIxMDk4NzZcIixcbiAgICAgICAgXCJub21icmVcIjogXCJDTElFTlRFIEVKRU1QTE8gUy5BLiBERSBDLlYuXCJcbiAgICB9LFxuICAgIFwicmVzdW1lblwiOiB7XG4gICAgICAgIFwidG90YWxQYWdhclwiOiAxMTNcbiAgICB9XG59In0.oz9T56xhFI28mUP0-HFDQ7eV-IjJgM7BKL2YVYnPdA2cLn5Hz6gvtKxVEXD91kkHAUFAhfc1FJOGbHkeuYBnRlnxut0znH6wPsVcALUBv2euPaNJKBFSOBaHPTQLjWJi3z2sb1Kozx5V1kU10Ux5tuK9q9jndPVEPYAHVAL5iYkfhtmcQR6cZn-4WS6lygJzhvJH1PxKRoWTt4vBPlAXb5ArCLFq9YDLCe8WDTJ_H4THpI2mLl5A42pD8k2SGtNHfMB8a4q57pIql5oSyrpBIV3czH2H7zOMtFhhVNdoCwSGL6RQ4AoQBSTYm4V-KHNUYtNle67tuVD2BtkZ36biLw"
}

JWS Token Structure

The signed JWS token follows the standard format: header.payload.signature

  • Header: {"typ":"JWT","alg":"RS512"} (Base64URL encoded)
  • Payload: DTE JSON data as pretty-printed string (Base64URL encoded)
  • Signature: RSA-SHA512 signature (Base64URL encoded)

Error Response Example

{
    "success": false,
    "message": "Request validation failed",
    "errorCode": "COD_803",
    "errors": [
        "NIT must be exactly 14 characters long",
        "Password must be at least 8 characters long"
    ]
}

Examples

See the examples/ directory for working examples:

  • Basic Usage: examples/basic_usage.php
  • File Usage: examples/file_usage.php
  • Error Handling: examples/error_handling.php

Running Examples

Prerequisites: Make sure you have generated test certificates first:

php examples/mock_certificate_generator.php

Then run any example:

# Basic DTE signing
php examples/basic_usage.php

# Sign from JSON file
php examples/file_usage.php

# Error handling demonstration
php examples/error_handling.php

Expected output: Each example will show the signing process and display the resulting JWS token.

Testing

Test Results

11 tests | ✅ 23 assertions | ✅ 100% pass rate | ⚡ <100ms execution time

Run the test suite:

composer test

Run with coverage:

composer test:coverage

Run static analysis:

composer analyse

Run all checks (tests + analysis):

composer check

Test Coverage

  • Unit tests for all core components
  • Integration tests with mock certificates
  • Error handling validation
  • Request/response format verification

Security Considerations

  • 🔐 Certificates: Store certificates securely with proper file permissions
  • 🔒 Passwords: Use strong passwords and secure password management
  • 🚫 Logging: Never log passwords or private keys
  • Validation: Always validate input data
  • 🧹 Memory: Sensitive data is cleared from memory after use

Production Readiness

✅ Ready for Production

  • El Salvador DTE specification compliance
  • JWS RS512 signing implementation
  • Input validation and error handling
  • Security best practices implemented
  • Performance requirements met (<500ms)
  • Unit and integration tests
  • PSR-12 code standards

🔧 Production Setup Checklist

  • Use real certificates from Ministro de Hacienda de El Salvador
  • Secure certificate storage with proper permissions (600/700)
  • Environment-specific configuration
  • Production logging setup (without sensitive data)
  • Error monitoring and alerting
  • Performance monitoring
  • Regular security updates

📋 Dependencies

  • Runtime: PHP 8.1+, OpenSSL, libxml
  • Library: firebase/php-jwt v6.11+ for JWS implementation
  • Compatible: Tested with PHP 8.4.11
  • Development: PHPUnit 10.5+, PHPStan 1.10+ for quality assurance

Development

Project Structure

src/
├── DteSigner.php              # Main SDK class
├── Certificate/               # Certificate handling
├── Signing/                   # JWS signing engine
├── Validators/                # Input validation
├── Exceptions/                # Custom exceptions
└── Utils/                     # Utility classes
examples/                      # Working examples
tests/                         # PHPUnit tests
certificates/                  # Default certificate directory

Code Style

This project follows PSR-12 coding standards and uses:

  • English naming conventions
  • Type hints for all parameters and return values
  • Constants for magic numbers
  • Dependency injection pattern

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes following PSR-12
  4. Add tests for new functionality
  5. Submit a pull request

License

MIT License. See LICENSE file for details.

Support

For issues and questions:

  • GitHub Issues: Report a bug
  • Documentation: See examples/ directory

Disclaimer

This SDK implements the complete DTE signing specification and is production-ready from a technical standpoint. However:

  • Ensure compliance with current El Salvador Ministro de Hacienda de El Salvador regulations
  • Use official certificates provided by the Ministro de Hacienda de El Salvador for production
  • Perform additional security audits as required by your organization
  • The examples use mock certificates for demonstration purposes only

For production deployment, review all security considerations and follow the production setup checklist above.