Issues (386)

Security Analysis    21 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation (2)
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection (1)
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure (4)
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection (13)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/SimpleSAML/Metadata/SAMLBuilder.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Metadata;
6
7
use DOMElement;
8
use SimpleSAML\{Configuration, Module, Logger, Utils};
9
use SimpleSAML\Assert\{Assert, AssertionFailedException};
10
use SimpleSAML\Module\adfs\SAML2\XML\fed\SecurityTokenServiceType;
11
use SimpleSAML\SAML2\Constants as C;
12
use SimpleSAML\SAML2\Exception\ArrayValidationException;
13
use SimpleSAML\SAML2\XML\idpdisc\DiscoveryResponse;
14
use SimpleSAML\SAML2\XML\md\{AbstractIndexedEndpointType, ContactPerson, Extensions, KeyDescriptor, NameIDFormat};
15
use SimpleSAML\SAML2\XML\md\{ArtifactResolutionService, AssertionConsumerService, AssertionIDRequestService};
16
use SimpleSAML\SAML2\XML\md\{AttributeConsumingService, AttributeService, SingleLogoutService, SingleSignOnService};
17
use SimpleSAML\SAML2\XML\md\{AttributeAuthorityDescriptor, EntityDescriptor, IDPSSODescriptor, SPSSODescriptor};
18
use SimpleSAML\SAML2\XML\md\{Organization, RequestedAttribute, RoleDescriptor, ServiceDescription, ServiceName};
19
use SimpleSAML\SAML2\XML\mdattr\EntityAttributes;
20
use SimpleSAML\SAML2\XML\mdrpi\RegistrationInfo;
21
use SimpleSAML\SAML2\XML\mdui\{DiscoHints, UIInfo};
22
use SimpleSAML\SAML2\XML\saml\{Attribute, AttributeValue};
23
use SimpleSAML\SAML2\XML\shibmd\Scope;
24
use SimpleSAML\XML\Chunk;
25
use SimpleSAML\XMLSecurity\XML\ds\{KeyInfo, KeyName, X509Certificate, X509Data};
26
27
use function array_key_exists;
28
use function array_keys;
29
use function array_map;
30
use function array_merge;
31
use function class_parents;
32
use function count;
33
use function in_array;
34
use function is_int;
35
use function preg_match;
36
use function time;
37
38
/**
39
 * Class for generating SAML 2.0 metadata from SimpleSAMLphp metadata arrays.
40
 *
41
 * This class builds SAML 2.0 metadata for an entity by examining the metadata for the entity.
42
 *
43
 * @package SimpleSAMLphp
44
 */
45
46
class SAMLBuilder
47
{
48
    /**
49
     * The EntityDescriptor we are building.
50
     *
51
     * @var \SimpleSAML\SAML2\XML\md\EntityDescriptor
52
     */
53
    private EntityDescriptor $entityDescriptor;
54
55
56
    /**
57
     * Initialize the SAML builder.
58
     *
59
     * @param string   $entityId The entity id of the entity.
60
     * @param int|null $maxCache The maximum time in seconds the metadata should be cached. Defaults to null
61
     * @param int|null $maxDuration The maximum time in seconds this metadata should be considered valid. Defaults
62
     * to null.
63
     */
64
    public function __construct(
65
        string $entityId,
66
        private ?int $maxCache = null,
67
        private ?int $maxDuration = null,
68
    ) {
69
        $this->entityDescriptor = new EntityDescriptor();
70
        $this->entityDescriptor->setEntityID($entityId);
71
    }
72
73
74
    /**
75
     * @param array $metadata
76
     */
77
    private function setExpiration(array $metadata): void
78
    {
79
        if (array_key_exists('expire', $metadata)) {
80
            if ($metadata['expire'] - time() < $this->maxDuration) {
81
                $this->maxDuration = $metadata['expire'] - time();
82
            }
83
        }
84
85
        if ($this->maxCache !== null) {
86
            $this->entityDescriptor->setCacheDuration('PT' . $this->maxCache . 'S');
87
        }
88
        if ($this->maxDuration !== null) {
89
            $this->entityDescriptor->setValidUntil(time() + $this->maxDuration);
90
        }
91
    }
92
93
94
    /**
95
     * Retrieve the EntityDescriptor element which is generated for this entity.
96
     *
97
     * @return \DOMElement The EntityDescriptor element of this entity.
98
     */
99
    public function getEntityDescriptor(): DOMElement
100
    {
101
        $xml = $this->entityDescriptor->toXML();
102
        $xml->ownerDocument->appendChild($xml);
103
104
        return $xml;
105
    }
106
107
108
    /**
109
     * Retrieve the EntityDescriptor as text.
110
     *
111
     * This function serializes this EntityDescriptor, and returns it as text.
112
     *
113
     * @param bool $formatted Whether the returned EntityDescriptor should be formatted first.
114
     *
115
     * @return string The serialized EntityDescriptor.
116
     */
117
    public function getEntityDescriptorText(bool $formatted = true): string
118
    {
119
        $xml = $this->getEntityDescriptor();
120
        if ($formatted) {
121
            $xmlUtils = new Utils\XML();
122
            $xmlUtils->formatDOMElement($xml);
123
        }
124
125
        $xml->ownerDocument->encoding = "utf-8";
126
127
        return $xml->ownerDocument->saveXML();
128
    }
129
130
131
    /**
132
     * Add a SecurityTokenServiceType for ADFS metadata.
133
     *
134
     * @param array $metadata The metadata with the information about the SecurityTokenServiceType.
135
     */
136
    public function addSecurityTokenServiceType(array $metadata): void
137
    {
138
        Assert::notNull($metadata['entityid']);
139
        Assert::notNull($metadata['metadata-set']);
140
141
        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
142
        $defaultEndpoint = $metadata->getDefaultEndpoint('SingleSignOnService');
143
144
        $e = new SecurityTokenServiceType();
145
        $e->setLocation($defaultEndpoint['Location']);
146
147
        $this->addCertificate($e, $metadata);
148
149
        $this->entityDescriptor->addRoleDescriptor($e);
150
    }
151
152
153
    /**
154
     * Add extensions to the metadata.
155
     *
156
     * @param \SimpleSAML\Configuration    $metadata The metadata to get extensions from.
157
     * @param \SimpleSAML\SAML2\XML\md\RoleDescriptor $e Reference to the element where the
158
     *   Extensions element should be included.
159
     */
160
    private function addExtensions(Configuration $metadata, RoleDescriptor $e): void
161
    {
162
        $extensions = [];
163
164
        if ($metadata->hasValue('scope')) {
165
            foreach ($metadata->getArray('scope') as $scopetext) {
166
                $isRegexpScope = (1 === preg_match('/[\$\^\)\(\*\|\\\\]/', $scopetext));
167
                $extensions[] = new Scope($scopetext, $isRegexpScope);
168
            }
169
        }
170
171
        if ($metadata->hasValue('EntityAttributes')) {
172
            $attr = [];
173
            foreach ($metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) {
174
                $attrValues = [];
175
                foreach ($attributeValues as $attributeValue) {
176
                    $attrValues[] = new AttributeValue($attributeValue);
177
                }
178
179
                // Attribute names that is not URI is prefixed as this: '{nameformat}name'
180
                if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) {
181
                    $attr[] = new Attribute(
182
                        name: $matches[2],
183
                        nameFormat: $matches[1] === C::NAMEFORMAT_UNSPECIFIED ? null : $matches[1],
184
                        attributeValue: $attrValues,
185
                    );
186
                } else {
187
                    $attr[] = new Attribute(
188
                        name: $attributeName,
189
                        nameFormat: C::NAMEFORMAT_URI,
190
                        attributeValue: $attrValues,
191
                    );
192
                }
193
            }
194
195
            $extensions[] = new EntityAttributes($attr);
196
        }
197
198
        if ($metadata->hasValue('saml:Extensions')) {
199
            $chunks = $metadata->getArray('saml:Extensions');
200
            Assert::allIsInstanceOf($chunks, Chunk::class);
201
            $extensions = array_merge($extensions, $chunks);
202
        }
203
204
        if ($metadata->hasValue('RegistrationInfo')) {
205
            try {
206
                $extensions[] = RegistrationInfo::fromArray($metadata->getArray('RegistrationInfo'));
207
            } catch (ArrayValidationException $err) {
208
                Logger::error('Metadata: invalid content found in RegistrationInfo: ' . $err->getMessage());
209
            }
210
        }
211
212
        if ($metadata->hasValue('UIInfo')) {
213
            try {
214
                $extensions[] = UIInfo::fromArray($metadata->getArray('UIInfo'));
215
            } catch (ArrayValidationException $err) {
216
                Logger::error('Metadata: invalid content found in UIInfo: ' . $err->getMessage());
217
            }
218
        }
219
220
        if ($metadata->hasValue('DiscoHints')) {
221
            try {
222
                $extensions[] = DiscoHints::fromArray($metadata->getArray('DiscoHints'));
223
            } catch (ArrayValidationException $err) {
224
                Logger::error('Metadata: invalid content found in DiscoHints: ' . $err->getMessage());
225
            }
226
        }
227
228
        $e->setExtensions(new Extensions($extensions));
229
    }
230
231
232
    /**
233
     * Add an Organization element based on metadata array.
234
     *
235
     * @param array $metadata The metadata we should extract the organization information from.
236
     */
237
    public function addOrganizationInfo(array $metadata): void
238
    {
239
        if (
240
            empty($metadata['OrganizationName']) ||
241
            empty($metadata['OrganizationDisplayName']) ||
242
            empty($metadata['OrganizationURL'])
243
        ) {
244
            // empty or incomplete organization information
245
            return;
246
        }
247
248
        $arrayUtils = new Utils\Arrays();
249
        $org = null;
250
251
        try {
252
            $org = Organization::fromArray([
253
                'OrganizationName' => $arrayUtils->arrayize($metadata['OrganizationName'], 'en'),
254
                'OrganizationDisplayName' => $arrayUtils->arrayize($metadata['OrganizationDisplayName'], 'en'),
255
                'OrganizationURL' => $arrayUtils->arrayize($metadata['OrganizationURL'], 'en'),
256
            ]);
257
        } catch (ArrayValidationException $e) {
258
            Logger::error('Federation: invalid content found in contact: ' . $e->getMessage());
259
        }
260
261
        $this->entityDescriptor->setOrganization($org);
262
    }
263
264
265
    /**
266
     * Add a list of endpoints to metadata.
267
     *
268
     * @param array $endpoints The endpoints.
269
     * @param class-string $class The type of endpoint to create
270
     *
271
     * @return array An array of endpoint objects,
272
     *     either \SimpleSAML\SAML2\XML\md\AbstractEndpointType or \SimpleSAML\SAML2\XML\md\AbstractIndexedEndpointType.
273
     */
274
    private static function createEndpoints(array $endpoints, string $class): array
275
    {
276
        $indexed = in_array(AbstractIndexedEndpointType::class, class_parents($class), true);
277
        $ret = [];
278
279
        // Set an index if it wasn't already set
280
        if ($indexed) {
281
            foreach ($endpoints as &$ep) {
282
                if (!isset($ep['index'])) {
283
                    // Find the maximum index
284
                    $maxIndex = -1;
285
                    foreach ($endpoints as $ep) {
0 ignored issues
show
Comprehensibility Bug introduced by
$ep is overwriting a variable from outer foreach loop.
Loading history...
286
                        if (!isset($ep['index'])) {
287
                            continue;
288
                        }
289
290
                        if ($ep['index'] > $maxIndex) {
291
                            $maxIndex = $ep['index'];
292
                        }
293
                    }
294
295
                    $ep['index'] = $maxIndex + 1;
296
                }
297
            }
298
        }
299
300
        foreach ($endpoints as $endpoint) {
301
            $ret[] = $class::fromArray($endpoint);
302
        }
303
304
        return $ret;
305
    }
306
307
308
    /**
309
     * Add an AttributeConsumingService element to the metadata.
310
     *
311
     * @param \SimpleSAML\SAML2\XML\md\SPSSODescriptor $spDesc The SPSSODescriptor element.
312
     * @param \SimpleSAML\Configuration     $metadata The metadata.
313
     */
314
    private function addAttributeConsumingService(
315
        SPSSODescriptor $spDesc,
316
        Configuration $metadata,
317
    ): void {
318
        $attributes = $metadata->getOptionalArray('attributes', []);
319
        $serviceName = $metadata->getOptionalLocalizedString('name', []);
320
321
        if (count($serviceName) === 0 || count($attributes) == 0) {
322
            // we cannot add an AttributeConsumingService without name and attributes
323
            return;
324
        }
325
326
        $attributesrequired = $metadata->getOptionalArray('attributes.required', []);
327
        $nameFormat = $metadata->getOptionalString('attributes.NameFormat', C::NAMEFORMAT_URI);
328
        $serviceDescription = $metadata->getOptionalLocalizedString('description', []);
329
330
        $requestedAttributes = [];
331
        foreach ($attributes as $friendlyName => $attribute) {
332
            $requestedAttributes[] = new RequestedAttribute(
333
                $attribute,
334
                in_array($attribute, $attributesrequired, true) ?: null,
335
                $nameFormat !== C::NAMEFORMAT_UNSPECIFIED ? $nameFormat : null,
336
                !is_int($friendlyName) ? $friendlyName : null,
337
            );
338
        }
339
340
        /**
341
         * Add an AttributeConsumingService element with information as name and description and list
342
         * of requested attributes
343
         */
344
        $attributeconsumer = new AttributeConsumingService(
345
            $metadata->getOptionalInteger('attributes.index', 0),
346
            array_map(
347
                function ($lang, $sName) {
348
                    return new ServiceName($lang, $sName);
349
                },
350
                array_keys($serviceName),
351
                $serviceName,
352
            ),
353
            $requestedAttributes,
354
            $metadata->hasValue('attributes.isDefault')
355
                ? $metadata->getOptionalBoolean('attributes.isDefault', false)
356
                : null,
357
            array_map(
358
                function ($lang, $sDesc) {
359
                    return new ServiceDescription($lang, $sDesc);
360
                },
361
                array_keys($serviceDescription),
362
                $serviceDescription,
363
            ),
364
        );
365
366
        $spDesc->addAttributeConsumingService($attributeconsumer);
367
    }
368
369
370
    /**
371
     * Add a specific type of metadata to an entity.
372
     *
373
     * @param string $set The metadata set this metadata comes from.
374
     * @param array  $metadata The metadata.
375
     */
376
    public function addMetadata(string $set, array $metadata): void
377
    {
378
        $this->setExpiration($metadata);
379
380
        switch ($set) {
381
            case 'saml20-sp-remote':
382
                $this->addMetadataSP20($metadata);
383
                break;
384
            case 'saml20-idp-remote':
385
                $this->addMetadataIdP20($metadata);
386
                break;
387
            case 'attributeauthority-remote':
388
                $this->addAttributeAuthority($metadata);
389
                break;
390
            default:
391
                Logger::warning('Unable to generate metadata for unknown type \'' . $set . '\'.');
392
        }
393
    }
394
395
396
    /**
397
     * Add SAML 2.0 SP metadata.
398
     *
399
     * @param array $metadata The metadata.
400
     * @param string[] $protocols The protocols supported. Defaults to \SimpleSAML\SAML2\Constants::NS_SAMLP.
401
     */
402
    public function addMetadataSP20(array $metadata, array $protocols = [C::NS_SAMLP]): void
403
    {
404
        Assert::notNull($metadata['entityid']);
405
        Assert::notNull($metadata['metadata-set']);
406
407
        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
408
409
        $e = new SPSSODescriptor();
410
        $e->setProtocolSupportEnumeration($protocols);
411
412
        if ($metadata->hasValue('saml20.sign.assertion')) {
413
            $e->setWantAssertionsSigned($metadata->getBoolean('saml20.sign.assertion'));
414
        }
415
416
        if ($metadata->hasValue('redirect.validate')) {
417
            $e->setAuthnRequestsSigned($metadata->getBoolean('redirect.validate'));
418
        } elseif ($metadata->hasValue('validate.authnrequest')) {
419
            $e->setAuthnRequestsSigned($metadata->getBoolean('validate.authnrequest'));
420
        }
421
422
        $this->addExtensions($metadata, $e);
423
424
        $this->addCertificate($e, $metadata);
425
426
        $e->setSingleLogoutService(self::createEndpoints(
427
            $metadata->getEndpoints('SingleLogoutService'),
428
            SingleLogoutService::class,
429
        ));
430
431
        $nids = [];
432
        foreach ($metadata->getOptionalArrayizeString('NameIDFormat', []) as $nid) {
433
            $nids[] = new NameIDFormat($nid);
434
        }
435
        $e->setNameIDFormat($nids);
436
437
        $endpoints = $metadata->getEndpoints('AssertionConsumerService');
438
        foreach ($metadata->getOptionalArrayizeString('AssertionConsumerService.artifact', []) as $acs) {
439
            $endpoints[] = [
440
                'Binding'  => C::BINDING_HTTP_ARTIFACT,
441
                'Location' => $acs,
442
            ];
443
        }
444
        $e->setAssertionConsumerService(self::createEndpoints($endpoints, AssertionConsumerService::class));
445
446
        $this->addAttributeConsumingService($e, $metadata);
447
448
        $this->entityDescriptor->addRoleDescriptor($e);
449
450
        foreach ($metadata->getOptionalArray('contacts', []) as $contact) {
451
            if (array_key_exists('ContactType', $contact) && array_key_exists('EmailAddress', $contact)) {
452
                $this->addContact(ContactPerson::fromArray($contact));
453
            }
454
        }
455
    }
456
457
458
    /**
459
     * Add metadata of a SAML 2.0 identity provider.
460
     *
461
     * @param array $metadata The metadata.
462
     */
463
    public function addMetadataIdP20(array $metadata): void
464
    {
465
        Assert::notNull($metadata['entityid']);
466
        Assert::notNull($metadata['metadata-set']);
467
468
        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
469
470
        $e = new IDPSSODescriptor();
471
        $e->setProtocolSupportEnumeration(array_merge($e->getProtocolSupportEnumeration(), [C::NS_SAMLP]));
472
473
        if ($metadata->hasValue('sign.authnrequest')) {
474
            $e->setWantAuthnRequestsSigned($metadata->getBoolean('sign.authnrequest'));
475
        } elseif ($metadata->hasValue('redirect.sign')) {
476
            $e->setWantAuthnRequestsSigned($metadata->getBoolean('redirect.sign'));
477
        }
478
479
        if ($metadata->hasValue('errorURL')) {
480
            $e->setErrorURL($metadata->getString('errorURL'));
481
        } else {
482
            $e->setErrorURL(Module::getModuleURL(
483
                'core/error/ERRORURL_CODE?ts=ERRORURL_TS&rp=ERRORURL_RP&tid=ERRORURL_TID&ctx=ERRORURL_CTX',
484
            ));
485
        }
486
487
        $this->addExtensions($metadata, $e);
488
489
        $this->addCertificate($e, $metadata);
490
491
        if ($metadata->hasValue('ArtifactResolutionService')) {
492
            $e->setArtifactResolutionService(self::createEndpoints(
493
                $metadata->getEndpoints('ArtifactResolutionService'),
494
                ArtifactResolutionService::class,
495
            ));
496
        }
497
498
        $e->setSingleLogoutService(self::createEndpoints(
499
            $metadata->getEndpoints('SingleLogoutService'),
500
            SingleLogoutService::class,
501
        ));
502
503
        $nids = [];
504
        foreach ($metadata->getOptionalArrayizeString('NameIDFormat', []) as $nid) {
505
            $nids[] = new NameIDFormat($nid);
506
        }
507
        $e->setNameIDFormat($nids);
508
509
        $e->setSingleSignOnService(self::createEndpoints(
510
            $metadata->getEndpoints('SingleSignOnService'),
511
            SingleSignOnService::class,
512
        ));
513
514
        $this->entityDescriptor->addRoleDescriptor($e);
515
516
        foreach ($metadata->getOptionalArray('contacts', []) as $contact) {
517
            if (array_key_exists('ContactType', $contact) && array_key_exists('EmailAddress', $contact)) {
518
                try {
519
                    $this->addContact(ContactPerson::fromArray($contact));
520
                } catch (ArrayValidationException $e) {
521
                    Logger::error('IdP Metadata: invalid content found in contact: ' . $e->getMessage());
522
                    continue;
523
                }
524
            }
525
        }
526
    }
527
528
529
    /**
530
     * Add metadata of a SAML attribute authority.
531
     *
532
     * @param array $metadata The AttributeAuthorityDescriptor, in the format returned by
533
     * \SimpleSAML\Metadata\SAMLParser.
534
     */
535
    public function addAttributeAuthority(array $metadata): void
536
    {
537
        Assert::notNull($metadata['entityid']);
538
        Assert::notNull($metadata['metadata-set']);
539
540
        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
541
542
        $e = new AttributeAuthorityDescriptor();
543
        $e->setProtocolSupportEnumeration($metadata->getOptionalArray('protocols', [C::NS_SAMLP]));
544
545
        $this->addExtensions($metadata, $e);
546
        $this->addCertificate($e, $metadata);
547
548
        $e->setAttributeService(self::createEndpoints(
549
            $metadata->getEndpoints('AttributeService'),
550
            AttributeService::class,
551
        ));
552
        $e->setAssertionIDRequestService(self::createEndpoints(
553
            $metadata->getEndpoints('AssertionIDRequestService'),
554
            AssertionIDRequestService::class,
555
        ));
556
557
        $nids = [];
558
        foreach ($metadata->getOptionalArrayizeString('NameIDFormat', []) as $nid) {
559
            $nids[] = new NameIDFormat($nid);
560
        }
561
        $e->setNameIDFormat($nids);
562
563
        $this->entityDescriptor->addRoleDescriptor($e);
564
    }
565
566
567
    /**
568
     * Add contact information.
569
     *
570
     * @param \SimpleSAML\SAML2\XML\md\ContactPerson $contact The details about the contact.
571
     */
572
    public function addContact(ContactPerson $contact): void
573
    {
574
        $this->entityDescriptor->addContactPerson($contact);
575
    }
576
577
578
    /**
579
     * Add a KeyDescriptor with an X509 certificate.
580
     *
581
     * @param \SimpleSAML\SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
582
     * @param string                      $use The value of the 'use' attribute.
583
     * @param string                      $x509cert The certificate data.
584
     * @param string|null                 $keyName The name of the key. Should be valid for usage in an ID attribute,
585
     *                                             e.g. not start with a digit.
586
     */
587
    private function addX509KeyDescriptor(
588
        RoleDescriptor $rd,
589
        string $use,
590
        string $x509cert,
591
        ?string $keyName = null,
592
    ): void {
593
        Assert::oneOf($use, ['encryption', 'signing']);
594
        $info = [
595
            new X509Data([
596
                new X509Certificate($x509cert),
597
            ]),
598
        ];
599
        if ($keyName !== null) {
600
            $info[] = new KeyName($keyName);
601
        }
602
        $keyDescriptor = new KeyDescriptor(
603
            new KeyInfo($info),
604
            $use,
605
        );
606
        $rd->addKeyDescriptor($keyDescriptor);
607
    }
608
609
610
    /**
611
     * Add a certificate.
612
     *
613
     * Helper function for adding a certificate to the metadata.
614
     *
615
     * @param \SimpleSAML\SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
616
     * @param \SimpleSAML\Configuration    $metadata The metadata of the entity.
617
     */
618
    private function addCertificate(RoleDescriptor $rd, Configuration $metadata): void
619
    {
620
        $keys = $metadata->getPublicKeys();
621
        foreach ($keys as $key) {
622
            if ($key['type'] !== 'X509Certificate') {
623
                continue;
624
            }
625
            if (!isset($key['signing']) || $key['signing'] === true) {
626
                $this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate'], $key['name'] ?? null);
627
            }
628
            if (!isset($key['encryption']) || $key['encryption'] === true) {
629
                $this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate'], $key['name'] ?? null);
630
            }
631
        }
632
633
        if ($metadata->hasValue('https.certData')) {
634
            $this->addX509KeyDescriptor($rd, 'signing', $metadata->getString('https.certData'));
635
        }
636
    }
637
}
638