Passed
Push — master ( d6f1be...5f55f1 )
by Tim
02:26
created

HTTPRedirect::validateSignature()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 4
nop 2
dl 0
loc 18
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2;
6
7
use DOMElement;
8
use Exception;
9
use Psr\Http\Message\ServerRequestInterface;
10
use SimpleSAML\Assert\Assert;
11
use SimpleSAML\SAML2\Compat\ContainerSingleton;
12
use SimpleSAML\SAML2\Constants as C;
13
use SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException;
14
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
15
use SimpleSAML\SAML2\XML\samlp\AbstractRequest;
16
use SimpleSAML\SAML2\XML\samlp\MessageFactory;
17
use SimpleSAML\XML\DOMDocumentFactory;
18
use SimpleSAML\XMLSecurity\Key\AbstractKey;
19
use SimpleSAML\XMLSecurity\Key\PublicKey;
20
use SimpleSAML\XMLSecurity\Utils\Security;
21
22
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
23
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
24
25
use function array_key_exists;
26
use function base64_decode;
27
use function base64_encode;
28
use function gzdeflate;
29
use function gzinflate;
30
use function sprintf;
31
use function strlen;
32
use function strpos;
33
use function urlencode;
34
35
/**
36
 * Class which implements the HTTP-Redirect binding.
37
 *
38
 * @package simplesamlphp/saml2
39
 */
40
class HTTPRedirect extends Binding
41
{
42
    /**
43
     * Create the redirect URL for a message.
44
     *
45
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message.
46
     * @return string The URL the user should be redirected to in order to send a message.
47
     */
48
    public function getRedirectURL(AbstractMessage $message): string
49
    {
50
        if ($this->destination === null) {
51
            $destination = $message->getDestination();
52
            if ($destination === null) {
53
                throw new Exception('Cannot build a redirect URL, no destination set.');
54
            }
55
        } else {
56
            $destination = $this->destination;
57
        }
58
59
        $relayState = $message->getRelayState();
60
        $msgStr = $message->toXML();
61
62
        Utils::getContainer()->debugMessage($msgStr, 'out');
63
        $msgStr = $msgStr->ownerDocument?->saveXML($msgStr);
64
65
        $msgStr = gzdeflate($msgStr);
66
        $msgStr = base64_encode($msgStr);
67
68
        /* Build the query string. */
69
70
        if ($message instanceof AbstractRequest) {
71
            $msg = 'SAMLRequest=';
72
        } else {
73
            $msg = 'SAMLResponse=';
74
        }
75
        $msg .= urlencode($msgStr);
76
77
        if ($relayState !== null) {
78
            $msg .= '&RelayState=' . urlencode($relayState);
79
        }
80
81
        $signature = $message->getSignature();
82
        if ($signature !== null) { // add the signature
83
            $msg .= '&SigAlg=' . urlencode($signature->getSignedInfo()->getSignatureMethod()->getAlgorithm());
84
            $msg .= '&Signature=' . urlencode($signature->getSignatureValue()->getContent());
85
        }
86
87
        if (strpos($destination, '?') === false) {
88
            $destination .= '?' . $msg;
89
        } else {
90
            $destination .= '&' . $msg;
91
        }
92
93
        return $destination;
94
    }
95
96
97
    /**
98
     * Send a SAML 2 message using the HTTP-Redirect binding.
99
     * Note: This function never returns.
100
     *
101
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
102
     */
103
    public function send(AbstractMessage $message): void
104
    {
105
        $destination = $this->getRedirectURL($message);
106
        Utils::getContainer()->getLogger()->debug('Redirect to ' . strlen($destination) . ' byte URL: ' . $destination);
107
        Utils::getContainer()->redirect($destination);
108
    }
109
110
111
    /**
112
     * Receive a SAML 2 message sent using the HTTP-Redirect binding.
113
     *
114
     * Throws an exception if it is unable receive the message.
115
     *
116
     * @param \Psr\Http\Message\ServerRequestInterface $request
117
     * @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage The received message.
118
     * @throws \Exception
119
     *
120
     * NPath is currently too high but solving that just moves code around.
121
     */
122
    public function receive(ServerRequestInterface $request): AbstractMessage
123
    {
124
        $query = $request->getQueryParams();
125
126
        if (array_key_exists('SAMLRequest', $query)) {
127
            $message = $query['SAMLRequest'];
128
            $signedQuery = 'SAMLRequest=' . urlencode($query['SAMLRequest']);
129
        } elseif (array_key_exists('SAMLResponse', $query)) {
130
            $message = $query['SAMLResponse'];
131
            $signedQuery = 'SAMLResponse=' . urlencode($query['SAMLResponse']);
132
        } else {
133
            throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
134
        }
135
136
        if (isset($query['SAMLEncoding']) && $query['SAMLEncoding'] !== C::BINDING_HTTP_REDIRECT_DEFLATE) {
137
            throw new Exception(sprintf('Unknown SAMLEncoding: %s', $query['SAMLEncoding']));
138
        }
139
140
        $message = base64_decode($message);
141
        if ($message === false) {
142
            throw new Exception('Error while base64 decoding SAML message.');
143
        }
144
145
        $message = gzinflate($message);
146
        if ($message === false) {
147
            throw new Exception('Error while inflating SAML message.');
148
        }
149
150
        $document = DOMDocumentFactory::fromString($message);
151
        Utils::getContainer()->debugMessage($document->documentElement, 'in');
152
        $message = MessageFactory::fromXML($document->documentElement);
153
154
        if (array_key_exists('RelayState', $query)) {
155
            $message->setRelayState($query['RelayState']);
156
            $signedQuery .= '&RelayState=' . urlencode($query['RelayState']);
157
        }
158
159
        if (!array_key_exists('Signature', $query)) {
160
            return $message;
161
        }
162
163
        if (!array_key_exists('SigAlg', $query)) {
164
            throw new Exception('Missing signature algorithm.');
165
        } else {
166
            $signedQuery .= '&SigAlg=' . urlencode($query['SigAlg']);
167
        }
168
169
        $container = ContainerSingleton::getInstance();
170
        $blacklist = $container->getBlacklistedEncryptionAlgorithms();
171
        $verifier = (new SignatureAlgorithmFactory($blacklist))->getAlgorithm(
172
            $query['SigAlg'],
173
            // TODO:  Need to use the key from the metadata
174
            PEMCertificatesMock::getPublicKey(PEMCertificatesMock::SELFSIGNED_PUBLIC_KEY),
175
        );
176
177
        if ($verifier->verify($signedQuery, base64_decode($query['Signature'])) === false) {
178
            throw new SignatureVerificationFailedException('Failed to verify signature.');
179
        }
180
181
        return $message;
182
    }
183
}
184