Installation:
composer require greenter/xmldsig
Ensure vendor/autoload.php is autoloaded (Laravel handles this via composer dump-autoload).
Certificate Preparation:
.pfx certificate to .pem (private key + certificate) using the provided guide..pem file in a secure location (e.g., storage/app/certificates/).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'));
Signing XML:
signFromFile() for direct file operations.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]);
Integration with Laravel Services:
// 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;
});
}
public function generateInvoice(Request $request)
{
$xml = $this->generateXmlFromRequest($request);
$signedXml = app(SignedXml::class)->signXml($xml);
return response()->xml($signedXml)->header('Content-Type', 'application/xml');
}
Batch Processing:
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()]);
}
}
Validation:
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);
}
Certificate Format:
XMLSecurityKey::loadKey() fails silently if the .pem file is malformed..pem file using OpenSSL:
openssl rsa -in certificate.pem -check
-----BEGIN PRIVATE KEY----- or -----BEGIN CERTIFICATE----- headers. Use the CONVERT.md guide strictly.XML Structure:
signXml() may fail if the XML lacks a root node or has malformed namespaces.SimpleXmlHelper or DOMDocument to pre-validate:
$xml = simplexml_load_string($xmlString);
if ($xml === false) {
throw new \InvalidArgumentException("Invalid XML structure");
}
Performance:
Storage facade to handle large files:
$stream = Storage::disk('local')->readStream('large_invoice.xml');
$signedXml = $signer->signFromStream($stream); // Hypothetical; extend the class if needed.
Thread Safety:
SignedXml class is not thread-safe. Avoid instantiating it in parallel jobs or queues without synchronization.Enable Verbose Logging:
$signer->setCanonicalizationMethod(XMLSecurityDSig::EXCL_C14N);
$signer->setSignatureKeyInfoId(''); // Debug: Disable KeyInfo to see raw signature
XMLSecurityKey or XMLSecurityDSig errors.Compare Signatures:
openssl dgst -sha256 -verify certificate.pem -signature signature.bin invoice.xml
signature.bin for comparison.Namespace Handling:
ds (for signatures). Ensure your XML includes:
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
SignedXml class to auto-inject namespaces.Custom Canonicalization:
$signer->setCanonicalizationMethod(XMLSecurityDSig::EXCL_C14N_OMIT_COMMENTS);
Add Metadata:
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);
}
}
Support for Other Formats:
.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));
}
Laravel Artisan Command:
// 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!');
}
}
php artisan invoice:sign 1
How can I help you explore Laravel packages today?