Weave 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

Xmlseclibs Laravel Package

robrichards/xmlseclibs

xmlseclibs is a PHP library for XML Encryption and Signatures, offering safe-by-default verification, pinned keys, algorithm allowlists, RSA-OAEP/AES-GCM support, and low-level primitives for legacy XML security workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin. There is no Laravel service provider or facade, and there shouldn't be — this is a pure PHP primitive library. Add it and keep the constraint explicit, because the 4.x line hardens defaults in ways that change behaviour:

composer require robrichards/xmlseclibs:^4.0

The library is RobRichards\XMLSecLibs\ (note the XMLSecLibs segment, which does not match the xmlseclibs package name — an easy autocomplete miss). Three classes carry almost all day-to-day weight: XMLSecurityDSig (sign/verify), XMLSecurityKey (algorithms and key material), XMLSecEnc (encrypt/decrypt).

Where to look first. Skip the "Basic usage" signing example at first and read the README's Security notes section top to bottom — it is effectively the migration guide from every tutorial you will find online, most of which were written for 2.x/3.x and teach the exact patterns (trusting KeyInfo, boolean-checking verify(), accepting http://www.w3.org/2000/09/xmldsig#rsa-sha1) that 4.0 exists to kill. Then read the verifyDocument() and decryption recipes; those two code blocks are 90% of production usage.

First use case: verifying an inbound SAML response. This is the entry point for nearly every Laravel app that reaches for this package (via a hand-rolled SP, or while debugging OneLogin/SimpleSAMLphp integration). Put the trust anchor in config/ pointing at an env var, never in the repository:

// config/saml.php
return [
    'idp_cert' => env('SAML_IDP_CERT'),          // path or raw PEM
    'sp_key'   => env('SAML_SP_KEY'),
    'sp_cert'  => env('SAML_SP_CERT'),
];

Then a single collaborator that owns the DOMDocument lifecycle:

namespace App\Support\Xml;

use DOMDocument;
use RobRichards\XMLSecLibs\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecurityKey;

class SignedXmlVerifier
{
    public function verify(string $xml, string $pinnedCertPem): array
    {
        $doc = new DOMDocument();
        // LIBXML_NONET: never let libxml fetch anything. Never pass LIBXML_NOENT or LIBXML_DTDLOAD.
        if (! $doc->loadXML($xml, LIBXML_NONET)) {
            throw new XmlVerificationException('Malformed XML');
        }

        $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'public']);
        $key->loadKey($pinnedCertPem, true, true);   // true = is cert, true = strict/no self-signed derivation

        $dsig = new XMLSecurityDSig();
        // SAML uses ID="...", not Id="...". Register it or reference lookup fails.
        $dsig->idKeys = ['ID'];

        return $dsig->verifyDocument($key, $doc);    // throws on failure
    }
}

verifyDocument() returns the array of validated DOM nodes. Return those, not the document. Your controller/resolver should then map those nodes into your own DTOs immediately and drop the document reference — that is the whole point of the API.

Implementation Patterns

Wire it as a single injected service, not inline in controllers. The verification object is stateful (id keys, algorithm allowlists, legacy flags) and constructing it per request in three different places guarantees they drift. Bind SignedXmlVerifier as a singleton only if you rebuild the XMLSecurityDSig per call; safer to leave it unbound and new the DSig inside the method.

Treat the validated node set as the security boundary. For SAML this means: after verification, read Assertion attributes (NotBefore, NotOnOrAfter, Audience, InResponseTo, Recipient) from the returned nodes, then issue your session. Do not getElementById() afterwards — that re-reads the untrusted document and is the classic assertion-injection bug.

[$assertion] = $verifier->verify($samlResponse, config('saml.idp_cert'));

$conditions = $assertion->getElementsByTagName('Conditions')->item(0);
// ... validate NotBefore/NotOnOrAfter against now, with ~2 min skew
// ... validate Audience against config('app.url')
// ... validate InResponseTo against the request ID you stored in session

Signing outgoing documents (AuthnRequests, SOAP bodies, custom XML exports). The flow is always: build the doc → addReference → sign → appendSignature → serialise. Reference the specific node, not the whole document, when the signature must cover only a fragment:

$dsig = new XMLSecurityDSig();
$dsig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);   // must match what the peer expects
$dsig->addReference(
    $doc->documentElement,                     // scope the digest
    XMLSecurityDSig::SHA256,
    ['http://www.w3.org/2000/09/xmldsig#enveloped-signature']
);

$key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
$key->passphrase = config('saml.sp_key_passphrase');   // null is fine
$key->loadKey(config('saml.sp_key'), true);

$dsig->sign($key);
$dsig->add509Cert(file_get_contents(config('saml.sp_cert')));  // for the peer's benefit only
$dsig->appendSignature($doc->documentElement);

$signedXml = $doc->saveXML();

In practice you wrap signing in a queued job or an HTTP client middleware when it feeds a SOAP integration — signing is CPU-bound and PEM parsing is not free, so cache the parsed XMLSecurityKey in a container singleton if you sign per-request at volume.

Decryption (WS-Security, encrypted assertions). Follow the README recipe verbatim: locateEncryptedData → locateKey → locateKeyInfo → unwrap with your private key → decryptNode($objKey, true). Pin the policy on the instance before decrypting:

$enc->allowedDataAlgorithms = XMLSecEnc::DEFAULT_DATA_ALGORITHMS;  // AES-GCM only
$enc->allowedKeyAlgorithms  = XMLSecEnc::DEFAULT_KEY_ALGORITHMS;   // RSA-OAEP only

If the recipient is selected by KeyName, read $objKeyInfo->name and map it to a configured local key — never to anything derived from the document.

Keep a thin interop adapter for legacy peers. Banks and government SOAP endpoints still emit RSA-SHA1 and RSA-1.5. Isolate the widening so it cannot leak into SAML handling:

// Only inside LegacySoapGateway, never in the SAML path
$dsig->allowedSignatureAlgorithms[] = XMLSecurityKey::RSA_SHA1;
$enc->allowRSA15KeyTransport = true;

Where the widening is broad and you cannot enumerate exceptions, enableLegacyMode() exists as a temporary bridging call — but scope it to that one gateway class and open a ticket to remove it.

Testing. Generate throwaway keypairs in the test bootstrap with openssl_pkey_new, commit signed XML fixtures under tests/Fixtures/, and assert both happy path and the negative cases: tampered payload, signature stripped, DOCTYPE injected, unexpected algorithm, KeyInfo swapped for an attacker cert. The negatives are cheap and they are the ones that regress.

Gotchas and Tips

verify() returns -1 on error, which is truthy. If you use the low-level API at all, compare with === 1. if ($dsig->verify($key)) is a vulnerability, not a typo. Prefer verifyDocument(), which throws.

SAML's ID vs xmlseclibs' Id. Default idKeys is ['Id']. SAML assertions use ID. WS-Security uses wsu:Id, which additionally needs $dsig->idNS populated with the wsu namespace. Missing this produces "reference not found" or, worse, a signature that validates over a node you never read.

DOCTYPE is rejected on verify by default (entity-reference / Id bypass, same class as CVE-2025-23369). If a partner feeds you documents with a DOCTYPE and verification suddenly fails after upgrading, do not reach for forbidDoctype = false first — confirm they actually need DTDs. Decrypted plaintext containing a DOCTYPE is rejected unconditionally and cannot be re-enabled.

XPath transforms are rejected by default. SAML and WS-Security never need them. If you flip allowXPathTransforms = true, you are enabling pre-authentication CPU burns on a document-supplied expression, capped only by maxXPathTransforms/maxXPathNamespaces. Only do it for a source you fully trust.

Decryption errors are deliberately uniform (Failure decrypting Data). Do not branch on exception text — you will be building the padding oracle the library removed. Log the generic message, fail closed.

Canonicalisation mismatches are the #1 "but it works in Postman" bug. Exclusive C14N (EXC_C14N) is the safe default and what most peers expect; inclusive C14N breaks the moment a namespace is added to an ancestor. Also, if a C#/SignedXml peer produces a signature you cannot verify, check for whitespace inside the signature template — 4.x added stripWhitespace and omit_uri for exactly this interop case.

Always load untrusted XML yourself with LIBXML_NONET and nothing else. The library cannot protect you from a document you parsed with LIBXML_NOENT.

add509Cert($url, true) does fetch. It is HTTP/HTTPS-only, blocks loopback/private/link-local/CGNAT, disables redirects, and needs ['allow_file_scheme' => true] for file://. A small DNS-rebinding window remains — only pass URLs from configuration you control, and prefer passing PEM content directly.

Don't derive trust from KeyInfo. The embedded certificate is attacker-controlled in any document you did not sign. verifyDocument() enforces this by requiring a pinned key; if you drop to primitives, you have to enforce it yourself.

4.0 is a beta line. APIs and defaults may still shift before final. Pin the exact version in composer.json, read the changelog on every bump, and keep the signed-XML test fixtures as your upgrade tripwire.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator