Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xmldsig Laravel Package

greenter/xmldsig

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require greenter/xmldsig
    

    Ensure vendor/autoload.php is autoloaded (Laravel handles this via composer dump-autoload).

  2. Certificate Preparation:

    • Convert your .pfx certificate to .pem (private key + certificate) using the provided guide.
    • Store the .pem file in a secure location (e.g., storage/app/certificates/).
  3. First Use Case: Sign an XML file (e.g., a SUNAT-compliant invoice) with a single line:

    $signedXml = app(\Greenter\XMLSecLibs\Sunat\SignedXml::class)
        ->setCertificateFromFile(storage_path('app/certificates/certificate.pem'))
        ->signFromFile(public_path('invoices/invoice.xml'));
    

Implementation Patterns

Core Workflow

  1. Signing XML:

    • Files: Use signFromFile() for direct file operations.
    • Strings: Use signXml() for in-memory XML strings (e.g., from a database or API response).
    // Example: Sign XML from a database record
    $xmlString = Invoice::find($id)->xml_content;
    $signedXml = $signer->signXml($xmlString);
    Invoice::find($id)->update(['xml_content' => $signedXml]);
    
  2. Integration with Laravel Services:

    • Service Provider: Bind the signer to the container for dependency injection:
      // app/Providers/AppServiceProvider.php
      public function register()
      {
          $this->app->singleton(SignedXml::class, function ($app) {
              $signer = new SignedXml();
              $signer->setCertificateFromFile(storage_path('app/certificates/certificate.pem'));
              return $signer;
          });
      }
      
    • Usage in Controllers:
      public function generateInvoice(Request $request)
      {
          $xml = $this->generateXmlFromRequest($request);
          $signedXml = app(SignedXml::class)->signXml($xml);
          return response()->xml($signedXml)->header('Content-Type', 'application/xml');
      }
      
  3. Batch Processing:

    • Process multiple invoices in a queue job:
      public function handle()
      {
          $invoices = Invoice::where('signed_at', null)->get();
          foreach ($invoices as $invoice) {
              $signedXml = app(SignedXml::class)->signXml($invoice->xml_content);
              $invoice->update(['xml_content' => $signedXml, 'signed_at' => now()]);
          }
      }
      
  4. Validation:

    • Verify signed XML before processing (e.g., in a middleware or service):
      use Greenter\XMLSecLibs\XMLSecurityKey;
      
      public function verifySignature($xmlString)
      {
          $doc = new \DOMDocument();
          $doc->loadXML($xmlString);
          $key = XMLSecurityKey::instance(XMLSecurityKey::RSA_SHA256);
          $key->loadKey(storage_path('app/certificates/certificate.pem'), true);
          return $key->verify($doc);
      }
      

Gotchas and Tips

Pitfalls

  1. Certificate Format:

    • Error: XMLSecurityKey::loadKey() fails silently if the .pem file is malformed.
    • Fix: Validate the .pem file using OpenSSL:
      openssl rsa -in certificate.pem -check
      
    • Common Issue: Missing -----BEGIN PRIVATE KEY----- or -----BEGIN CERTIFICATE----- headers. Use the CONVERT.md guide strictly.
  2. XML Structure:

    • Error: signXml() may fail if the XML lacks a root node or has malformed namespaces.
    • Fix: Ensure the XML is well-formed and conforms to SUNAT’s schema. Use Laravel’s SimpleXmlHelper or DOMDocument to pre-validate:
      $xml = simplexml_load_string($xmlString);
      if ($xml === false) {
          throw new \InvalidArgumentException("Invalid XML structure");
      }
      
  3. Performance:

    • Issue: Signing large XML files (e.g., >1MB) may time out or consume excessive memory.
    • Workaround: Stream the XML file in chunks or use Laravel’s Storage facade to handle large files:
      $stream = Storage::disk('local')->readStream('large_invoice.xml');
      $signedXml = $signer->signFromStream($stream); // Hypothetical; extend the class if needed.
      
  4. Thread Safety:

    • Warning: The SignedXml class is not thread-safe. Avoid instantiating it in parallel jobs or queues without synchronization.

Debugging Tips

  1. Enable Verbose Logging:

    • Temporarily enable XMLSecLibs logging to debug signature issues:
      $signer->setCanonicalizationMethod(XMLSecurityDSig::EXCL_C14N);
      $signer->setSignatureKeyInfoId(''); // Debug: Disable KeyInfo to see raw signature
      
    • Check Laravel logs for XMLSecurityKey or XMLSecurityDSig errors.
  2. Compare Signatures:

    • Use OpenSSL to verify the signature manually:
      openssl dgst -sha256 -verify certificate.pem -signature signature.bin invoice.xml
      
    • Extract the signature from the XML and save it to signature.bin for comparison.
  3. Namespace Handling:

    • SUNAT XML often uses namespaces like ds (for signatures). Ensure your XML includes:
      <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      
    • If missing, the package may silently fail. Add this manually or extend the SignedXml class to auto-inject namespaces.

Extension Points

  1. Custom Canonicalization:

    • Override the canonicalization method for SUNAT-specific requirements:
      $signer->setCanonicalizationMethod(XMLSecurityDSig::EXCL_C14N_OMIT_COMMENTS);
      
  2. Add Metadata:

    • Extend the SignedXml class to include custom attributes in the signature (e.g., signing timestamp):
      class CustomSignedXml extends SignedXml
      {
          public function signXml($xml)
          {
              $this->addObject('http://uri.id/signing-time', now()->toAtomString());
              return parent::signXml($xml);
          }
      }
      
  3. Support for Other Formats:

    • Add methods to handle .pfx files directly by integrating phpseclib or openssl:
      public function setCertificateFromPfx($pfxPath, $passphrase)
      {
          $privateKey = file_get_contents($pfxPath);
          // Use OpenSSL to extract PEM from PFX (simplified)
          $this->setCertificate($this->convertPfxToPem($privateKey, $passphrase));
      }
      
  4. Laravel Artisan Command:

    • Create a dedicated command for signing invoices:
      // app/Console/Commands/SignInvoice.php
      class SignInvoice extends Command
      {
          protected $signature = 'invoice:sign {id}';
          public function handle()
          {
              $invoice = Invoice::findOrFail($this->argument('id'));
              $signedXml = app(SignedXml::class)->signXml($invoice->xml_content);
              $invoice->update(['xml_content' => $signedXml]);
              $this->info('Invoice signed successfully!');
          }
      }
      
    • Run with:
      php artisan invoice:sign 1
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope