HTTPArtifact::validateSignature()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\Binding;
6
7
use DateInterval;
8
use Exception;
9
use Nyholm\Psr7\Response;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use SimpleSAML\Configuration;
13
use SimpleSAML\Metadata\MetaDataStorageHandler;
14
use SimpleSAML\Module\saml\Message as MSG;
15
use SimpleSAML\SAML2\Assert\Assert;
16
use SimpleSAML\SAML2\Binding;
17
use SimpleSAML\SAML2\Binding\RelayStateTrait;
18
use SimpleSAML\SAML2\SOAPClient;
19
use SimpleSAML\SAML2\Utils;
20
use SimpleSAML\SAML2\XML\saml\Issuer;
21
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
22
use SimpleSAML\SAML2\XML\samlp\Artifact;
23
use SimpleSAML\SAML2\XML\samlp\ArtifactResolve;
24
use SimpleSAML\SAML2\XML\samlp\ArtifactResponse;
25
use SimpleSAML\Store\StoreFactory;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Store\StoreFactory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use SimpleSAML\Utils\HTTP;
27
use SimpleSAML\XMLSecurity\XMLSecurityKey;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSecurity\XMLSecurityKey was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
29
use function array_key_exists;
30
use function base64_decode;
31
use function base64_encode;
32
use function bin2hex;
33
use function hexdec;
34
use function openssl_random_pseudo_bytes;
35
use function pack;
36
use function sha1;
37
use function substr;
38
use function var_export;
39
40
/**
41
 * Class which implements the HTTP-Artifact binding.
42
 *
43
 * @package simplesamlphp/saml2
44
 */
45
class HTTPArtifact extends Binding implements AsynchronousBindingInterface, RelayStateInterface
46
{
47
    use RelayStateTrait;
48
49
50
    /**
51
     * @var \SimpleSAML\Configuration
52
     */
53
    private Configuration $spMetadata;
54
55
56
    /**
57
     * Create the redirect URL for a message.
58
     *
59
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message.
60
     * @return string The URL the user should be redirected to in order to send a message.
61
     *
62
     * @throws \Exception
63
     */
64
    public function getRedirectURL(AbstractMessage $message): string
65
    {
66
        $config = Configuration::getInstance();
67
68
        $store = StoreFactory::getInstance($config->getString('store.type'));
69
        if ($store === false) {
70
            throw new Exception('Unable to send artifact without a datastore configured.');
71
        }
72
73
        $generatedId = pack('H*', bin2hex(openssl_random_pseudo_bytes(20)));
74
        $issuer = $message->getIssuer();
75
        if ($issuer === null) {
76
            throw new Exception('Cannot get redirect URL, no Issuer set in the message.');
77
        }
78
        $artifact = base64_encode("\x00\x04\x00\x00" . sha1($issuer->getContent(), true) . $generatedId);
79
        $artifactData = $message->toXML();
80
        $artifactDataString = $artifactData->ownerDocument?->saveXML($artifactData);
81
82
        $clock = Utils::getContainer()->getClock();
83
        $store->set('artifact', $artifact, $artifactDataString, $clock->now()->add(new DateInterval('PT15M')));
84
85
        $destination = $message->getDestination();
86
        if ($destination === null) {
87
            throw new Exception('Cannot get redirect URL, no destination set in the message.');
88
        }
89
90
        $params = ['SAMLart' => $artifact];
91
92
        $relayState = $this->getRelayState();
93
        if ($relayState !== null) {
94
            $params['RelayState'] = $relayState;
95
        }
96
97
        $httpUtils = new HTTP();
98
        return $httpUtils->addURLparameters($destination, $params);
99
    }
100
101
102
    /**
103
     * Send a SAML 2 message using the HTTP-Redirect binding.
104
     *
105
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
106
     * @return \Psr\Http\Message\ResponseInterface
107
     */
108
    public function send(AbstractMessage $message): ResponseInterface
109
    {
110
        $destination = $this->getRedirectURL($message);
111
        return new Response(303, ['Location' => $destination]);
112
    }
113
114
115
    /**
116
     * Receive a SAML 2 message sent using the HTTP-Artifact binding.
117
     *
118
     * Throws an exception if it is unable receive the message.
119
     *
120
     * @param \Psr\Http\Message\ServerRequestInterface $request
121
     * @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage The received message.
122
     *
123
     * @throws \Exception
124
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
125
     */
126
    public function receive(ServerRequestInterface $request): AbstractMessage
127
    {
128
        $query = $request->getQueryParams();
129
        if (array_key_exists('SAMLart', $query)) {
130
            $artifact = base64_decode($query['SAMLart'], true);
131
            $endpointIndex = bin2hex(substr($artifact, 2, 2));
132
            $sourceId = bin2hex(substr($artifact, 4, 20));
133
        } else {
134
            throw new Exception('Missing SAMLart parameter.');
135
        }
136
137
        /** @psalm-suppress UndefinedClass */
138
        $metadataHandler = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance());
139
140
        $idpMetadata = $metadataHandler->getMetaDataConfigForSha1($sourceId, 'saml20-idp-remote');
141
142
        if ($idpMetadata === null) {
143
            throw new Exception('No metadata found for remote provider with SHA1 ID: ' . var_export($sourceId, true));
144
        }
145
146
        $endpoint = null;
147
        foreach ($idpMetadata->getEndpoints('ArtifactResolutionService') as $ep) {
148
            if ($ep['index'] === hexdec($endpointIndex)) {
149
                $endpoint = $ep;
150
                break;
151
            }
152
        }
153
154
        if ($endpoint === null) {
155
            throw new Exception('No ArtifactResolutionService with the correct index.');
156
        }
157
158
        Utils::getContainer()->getLogger()->debug(
159
            "ArtifactResolutionService endpoint being used is := " . $endpoint['Location'],
160
        );
161
162
        /**
163
         * @psalm-suppress UndefinedClass
164
         * @psalm-suppress DocblockTypeContradiction
165
         */
166
        Assert::notEmpty($this->spMetadata, 'Cannot process received message without SP metadata.');
167
168
        /**
169
         * Set the request attributes
170
         */
171
        $issuer = new Issuer($this->spMetadata->getString('entityid'));
172
173
        // Construct the ArtifactResolve Request
174
        $ar = new ArtifactResolve(new Artifact($artifact), null, $issuer, null, '2.0', $endpoint['Location']);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type SimpleSAML\SAML2\XML\samlp\Artifact expected by parameter $artifact of SimpleSAML\SAML2\XML\sam...tResolve::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
        $ar = new ArtifactResolve(new Artifact($artifact), /** @scrutinizer ignore-type */ null, $issuer, null, '2.0', $endpoint['Location']);
Loading history...
Bug introduced by
'2.0' of type string is incompatible with the type SimpleSAML\SAML2\Type\SAMLAnyURIValue|null expected by parameter $destination of SimpleSAML\SAML2\XML\sam...tResolve::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
        $ar = new ArtifactResolve(new Artifact($artifact), null, $issuer, null, /** @scrutinizer ignore-type */ '2.0', $endpoint['Location']);
Loading history...
175
176
        // sign the request
177
        /** @psalm-suppress UndefinedClass */
178
        MSG::addSign($this->spMetadata, $idpMetadata, $ar); // Shoaib - moved from the SOAPClient.
179
180
        $soap = new SOAPClient();
181
182
        // Send message through SoapClient
183
        /** @var \SimpleSAML\SAML2\XML\samlp\ArtifactResponse $artifactResponse */
184
        $artifactResponse = $soap->send($ar, $this->spMetadata, $idpMetadata);
185
186
        if (!$artifactResponse->isSuccess()) {
187
            throw new Exception('Received error from ArtifactResolutionService.');
188
        }
189
190
        $samlResponse = $artifactResponse->getMessage();
191
        if ($samlResponse === null) {
192
            /* Empty ArtifactResponse - possibly because of Artifact replay? */
193
194
            throw new Exception('Empty ArtifactResponse received, maybe a replay?');
195
        }
196
197
        $samlResponse->addValidator([get_class($this), 'validateSignature'], $artifactResponse);
198
199
        $query = $request->getQueryParams();
200
        if (isset($query['RelayState'])) {
201
            $this->setRelayState($query['RelayState']);
202
        }
203
204
        return $samlResponse;
205
    }
206
207
208
    /**
209
     * @param \SimpleSAML\Configuration $sp
210
     */
211
    public function setSPMetadata(Configuration $sp): void
212
    {
213
        $this->spMetadata = $sp;
214
    }
215
216
217
    /**
218
     * A validator which returns true if the ArtifactResponse was signed with the given key
219
     *
220
     * @param \SimpleSAML\SAML2\XML\samlp\ArtifactResponse $message
221
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key
222
     */
223
    public static function validateSignature(ArtifactResponse $message, XMLSecurityKey $key): bool
224
    {
225
        // @todo verify if this works and/or needs to do anything more. Ref. HTTPRedirect binding
226
        return $message->validate($key);
227
    }
228
}
229