Verify digital signature in SAML response against certificate in PHP
An XML signed using xmldsig syntax has 3 important parts:
Signature -> KeyInfo
contains information about the public key derived from the private key used to sign the dataSignature -> 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
Signature -> SignatureValue
contains the value of the signature generated by signingSignature -> 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