Verify digital signature in SAML response against certificate in PHP Verify digital signature in SAML response against certificate in PHP php php

Verify digital signature in SAML response against certificate in PHP


An XML signed using xmldsig syntax has 3 important parts:

  1. Signature -> KeyInfo contains information about the public key derived from the private key used to sign the data
  2. Signature -> SignedInfo contains the data which is gonna be signed using the private key mentioned above; the data contains information about how the verification should be computed, such as: CanonicalizationMethod, SignatureMethod, Reference
  3. Signature -> SignatureValue contains the value of the signature generated by signing Signature -> SignedInfo with the private key

Theoretically this is how the code should look for an rsa-sha1 algorithm(specified by Signature -> SignedInfo -> SignatureMethod), having the following canonicalization method: Exclusive XML Canonicalization 1.0 (omits comments), and the x509 certificate provided:

$xmlDoc = new DOMDocument();$xmlDoc->loadXML($xmlString);$xpath = new DOMXPath($xmlDoc);$xpath->registerNamespace('secdsig', 'http://www.w3.org/2000/09/xmldsig#');// fetch Signature node from XML$query = ".//secdsig:Signature";$nodeset = $xpath->query($query, $xmlDoc);$signatureNode = $nodeset->item(0);// fetch SignedInfo node from XML$query = "./secdsig:SignedInfo";$nodeset = $xpath->query($query, $signatureNode);$signedInfoNode = $nodeset->item(0);// canonicalize SignedInfo using the method descried in// ./secdsig:SignedInfo/secdsig:CanonicalizationMethod/@Algorithm$signedInfoNodeCanonicalized = $signedInfoNode->C14N(true, false);// fetch the x509 certificate from XML$query = 'string(./secdsig:KeyInfo/secdsig:X509Data/secdsig:X509Certificate)';$x509cert = $xpath->evaluate($query, $signatureNode);// we have to re-wrap the certificate from XML to respect the PEM standard$x509cert = "-----BEGIN CERTIFICATE-----\n"    . $x509cert . "\n"    . "-----END CERTIFICATE-----";// fetch public key from x509 certificate$publicKey = openssl_get_publickey($x509cert);// fetch the signature from XML$query = 'string(./secdsig:SignatureValue)';$signature = base64_decode($xpath->evaluate($query, $signatureNode));// verify the signature$ok = openssl_verify($signedInfoNodeCanonicalized, $signature, $publicKey);    

This lib does a good job at implementing xmldsig in php: xmlseclibs; an example of how to verify an xmldsig can be found here: https://github.com/robrichards/xmlseclibs/blob/master/tests/xmlsec-verify.phpt. This library also validates the digest value from Signature -> SignedInfo -> Reference, a step which I omitted above.


I would suggest you use https://github.com/lightSAML/lightSAML. It's using xmlseclibs and implements full SAML SSO SP profile. Simple receiving of SAML response from the HTTP POST and verifying its signature samples are given in LightSAML cookbook http://www.lightsaml.com/LightSAML-Core/Cookbook/How-to-receive-SAML-message/ and http://www.lightsaml.com/LightSAML-Core/Cookbook/How-to-verify-signature-of-SAML-message/ and the whole code would look like this

$request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();$bindingFactory = new \LightSaml\Binding\BindingFactory();$binding = $bindingFactory->getBindingByRequest($request);$messageContext = new \LightSaml\Context\Profile\MessageContext();/** @var \LightSaml\Model\Protocol\Response $response */$response = $binding->receive($request, $messageContext);$key = \LightSaml\Credential\KeyHelper::createPublicKey(    \LightSaml\Credential\X509Certificate::fromFile(__DIR__.'/../web/sp/saml.crt'));/** @var \LightSaml\Model\XmlDSig\SignatureXmlReader $signatureReader */$signatureReader = $authnRequest->getSignature();try {    $ok = $signatureReader->validate($key);    if ($ok) {        print "Signaure OK\n";    } else {        print "Signature not validated";    }} catch (\Exception $ex) {    print "Signature validation failed\n";}

Handling of the Response by full SAML SSO profile specificaction is a bit more than that, for those details, you can check the sample in https://github.com/lightSAML/lightSAML/blob/master/web/sp/acs.php or if you're using Symfony https://github.com/lightSAML/SpBundle