1<?php
2
3/**
4 * Determine if the SAML response is valid using a provided x509 certificate.
5 */
6class OneLogin_Saml_XmlSec
7{
8    /**
9     * A SamlResponse class provided to the constructor.
10     * @var OneLogin_Saml_Settings
11     */
12    protected $_settings;
13
14    /**
15     * The document to be tested.
16     * @var DomDocument
17     */
18    protected $_document;
19
20    /**
21     * Construct the SamlXmlSec object.
22     *
23     * @param OneLogin_Saml_Settings $settings A SamlResponse settings object containing the necessary
24     *                                          x509 certicate to test the document.
25     * @param OneLogin_Saml_Response $response The document to test.
26     */
27    public function __construct(OneLogin_Saml_Settings $settings, OneLogin_Saml_Response $response)
28    {
29        $this->_settings = $settings;
30        $this->_document = clone $response->document;
31    }
32
33    /**
34     * Verify that the document only contains a single Assertion
35     *
36     * @return bool TRUE if the document passes.
37     */
38    public function validateNumAssertions()
39    {
40        $rootNode = $this->_document;
41        $assertionNodes = $rootNode->getElementsByTagName('Assertion');
42        return ($assertionNodes->length == 1);
43    }
44
45    /**
46     * Verify that the document is still valid according
47     *
48     * @return bool
49     */
50    public function validateTimestamps()
51    {
52        $rootNode = $this->_document;
53        $timestampNodes = $rootNode->getElementsByTagName('Conditions');
54        for ($i = 0; $i < $timestampNodes->length; $i++) {
55            $nbAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotBefore");
56            $naAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotOnOrAfter");
57            if ($nbAttribute && strtotime($nbAttribute->textContent) > time()) {
58                return false;
59            }
60            if ($naAttribute && strtotime($naAttribute->textContent) <= time()) {
61                return false;
62            }
63        }
64        return true;
65    }
66
67    /**
68     * @return bool
69     *
70     * @throws Exception
71     */
72    public function isValid()
73    {
74        $singleAssertion = $this->validateNumAssertions();
75        if (!$singleAssertion) {
76            throw new Exception('Multiple assertions are not supported');
77        }
78
79        $validTimestamps = $this->validateTimestamps();
80        if (!$validTimestamps) {
81            throw new Exception('Timing issues (please check your clock settings)');
82        }
83
84        $objXMLSecDSig = new XMLSecurityDSig();
85
86        $objDSig = $objXMLSecDSig->locateSignature($this->_document);
87        if (!$objDSig) {
88            throw new Exception('Cannot locate Signature Node');
89        }
90        $objXMLSecDSig->canonicalizeSignedInfo();
91        $objXMLSecDSig->idKeys = array('ID');
92
93        $objKey = $objXMLSecDSig->locateKey();
94        if (!$objKey) {
95            throw new Exception('We have no idea about the key');
96        }
97
98        try {
99            $objXMLSecDSig->validateReference();
100        } catch (Exception $e) {
101            throw new Exception('Reference Validation Failed');
102        }
103
104        XMLSecEnc::staticLocateKeyInfo($objKey, $objDSig);
105
106        $objKey->loadKey($this->_settings->idpPublicCertificate, false, true);
107
108        return ($objXMLSecDSig->verify($objKey) === 1);
109    }
110}
111