Passed
Push — master ( fb0d53...7c0351 )
by Jaime Pérez
03:11
created

Utils::addNameId()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
nc 8
nop 2
dl 0
loc 19
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace SAML2;
4
5
use RobRichards\XMLSecLibs\XMLSecEnc;
6
use RobRichards\XMLSecLibs\XMLSecurityDSig;
7
use RobRichards\XMLSecLibs\XMLSecurityKey;
8
use SAML2\Compat\ContainerSingleton;
9
use SAML2\Exception\RuntimeException;
10
use SAML2\XML\ds\KeyInfo;
11
use SAML2\XML\ds\X509Certificate;
12
use SAML2\XML\ds\X509Data;
13
use SAML2\XML\md\KeyDescriptor;
14
15
/**
16
 * Helper functions for the SAML2 library.
17
 *
18
 * @package SimpleSAMLphp
19
 */
20
class Utils
21
{
22
    /**
23
     * Check the Signature in a XML element.
24
     *
25
     * This function expects the XML element to contain a Signature-element
26
     * which contains a reference to the XML-element. This is common for both
27
     * messages and assertions.
28
     *
29
     * Note that this function only validates the element itself. It does not
30
     * check this against any local keys.
31
     *
32
     * If no Signature-element is located, this function will return false. All
33
     * other validation errors result in an exception. On successful validation
34
     * an array will be returned. This array contains the information required to
35
     * check the signature against a public key.
36
     *
37
     * @param  \DOMElement  $root The element which should be validated.
38
     * @throws \Exception
39
     * @return array|bool An array with information about the Signature-element.
40
     */
41
    public static function validateElement(\DOMElement $root)
42
    {
43
        /* Create an XML security object. */
44
        $objXMLSecDSig = new XMLSecurityDSig();
45
46
        /* Both SAML messages and SAML assertions use the 'ID' attribute. */
47
        $objXMLSecDSig->idKeys[] = 'ID';
48
49
        /* Locate the XMLDSig Signature element to be used. */
50
        $signatureElement = self::xpQuery($root, './ds:Signature');
51
        if (count($signatureElement) === 0) {
52
            /* We don't have a signature element ot validate. */
53
54
            return false;
55
        } elseif (count($signatureElement) > 1) {
56
            throw new \Exception('XMLSec: more than one signature element in root.');
57
        }
58
        $signatureElement = $signatureElement[0];
59
        $objXMLSecDSig->sigNode = $signatureElement;
60
61
        /* Canonicalize the XMLDSig SignedInfo element in the message. */
62
        $objXMLSecDSig->canonicalizeSignedInfo();
63
64
        /* Validate referenced xml nodes. */
65
        if (!$objXMLSecDSig->validateReference()) {
66
            throw new \Exception('XMLsec: digest validation failed');
67
        }
68
69
        /* Check that $root is one of the signed nodes. */
70
        $rootSigned = false;
71
        /** @var \DOMNode $signedNode */
72
        foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) {
73
            if ($signedNode->isSameNode($root)) {
74
                $rootSigned = true;
75
                break;
76
            } elseif ($root->parentNode instanceof \DOMDocument && $signedNode->isSameNode($root->ownerDocument)) {
77
                /* $root is the root element of a signed document. */
78
                $rootSigned = true;
79
                break;
80
            }
81
        }
82
        if (!$rootSigned) {
83
            throw new \Exception('XMLSec: The root element is not signed.');
84
        }
85
86
        /* Now we extract all available X509 certificates in the signature element. */
87
        $certificates = [];
88
        foreach (self::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
89
            $certData = trim($certNode->textContent);
90
            $certData = str_replace(["\r", "\n", "\t", ' '], '', $certData);
91
            $certificates[] = $certData;
92
        }
93
94
        $ret = [
95
            'Signature' => $objXMLSecDSig,
96
            'Certificates' => $certificates,
97
        ];
98
99
        return $ret;
100
    }
101
102
103
    /**
104
     * Helper function to convert a XMLSecurityKey to the correct algorithm.
105
     *
106
     * @param  XMLSecurityKey $key       The key.
107
     * @param  string         $algorithm The desired algorithm.
108
     * @param  string         $type      Public or private key, defaults to public.
109
     * @throws \Exception
110
     * @return XMLSecurityKey The new key.
111
     */
112
    public static function castKey(XMLSecurityKey $key, $algorithm, $type = 'public')
113
    {
114
        assert(is_string($algorithm));
115
        assert($type === "public" || $type === "private");
116
117
        // do nothing if algorithm is already the type of the key
118
        if ($key->type === $algorithm) {
119
            return $key;
120
        }
121
122
        if (!in_array($algorithm, [
123
            XMLSecurityKey::RSA_1_5,
124
            XMLSecurityKey::RSA_SHA1,
125
            XMLSecurityKey::RSA_SHA256,
126
            XMLSecurityKey::RSA_SHA384,
127
            XMLSecurityKey::RSA_SHA512
128
        ], true)) {
129
            throw new \Exception('Unsupported signing algorithm.');
130
        }
131
132
        $keyInfo = openssl_pkey_get_details($key->key);
133
        if ($keyInfo === false) {
134
            throw new \Exception('Unable to get key details from XMLSecurityKey.');
135
        }
136
        if (!isset($keyInfo['key'])) {
137
            throw new \Exception('Missing key in public key details.');
138
        }
139
140
        $newKey = new XMLSecurityKey($algorithm, ['type' => $type]);
141
        $newKey->loadKey($keyInfo['key']);
142
143
        return $newKey;
144
    }
145
146
147
    /**
148
     * Check a signature against a key.
149
     *
150
     * An exception is thrown if we are unable to validate the signature.
151
     *
152
     * @param array          $info The information returned by the validateElement()-function.
153
     * @param XMLSecurityKey $key  The publickey that should validate the Signature object.
154
     * @throws \Exception
155
     * @return void
156
     */
157
    public static function validateSignature(array $info, XMLSecurityKey $key)
158
    {
159
        assert(array_key_exists("Signature", $info));
160
161
        /** @var XMLSecurityDSig $objXMLSecDSig */
162
        $objXMLSecDSig = $info['Signature'];
163
164
        $sigMethod = self::xpQuery($objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod');
0 ignored issues
show
Bug introduced by
It seems like $objXMLSecDSig->sigNode can also be of type null; however, parameter $node of SAML2\Utils::xpQuery() does only seem to accept DOMNode, maybe add an additional type check? ( Ignorable by Annotation )

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

164
        $sigMethod = self::xpQuery(/** @scrutinizer ignore-type */ $objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod');
Loading history...
165
        if (empty($sigMethod)) {
166
            throw new \Exception('Missing SignatureMethod element.');
167
        }
168
        $sigMethod = $sigMethod[0];
169
        if (!$sigMethod->hasAttribute('Algorithm')) {
170
            throw new \Exception('Missing Algorithm-attribute on SignatureMethod element.');
171
        }
172
        $algo = $sigMethod->getAttribute('Algorithm');
173
174
        if ($key->type === XMLSecurityKey::RSA_SHA256 && $algo !== $key->type) {
175
            $key = self::castKey($key, $algo);
176
        }
177
178
        /* Check the signature. */
179
        if ($objXMLSecDSig->verify($key) !== 1) {
180
            throw new \Exception("Unable to validate Signature");
181
        }
182
    }
183
184
185
    /**
186
     * Do an XPath query on an XML node.
187
     *
188
     * @param  \DOMNode $node  The XML node.
189
     * @param  string  $query The query.
190
     * @return \DOMElement[]    Array with matching DOM nodes.
191
     */
192
    public static function xpQuery(\DOMNode $node, $query)
193
    {
194
        assert(is_string($query));
195
        static $xpCache = null;
196
197
        if ($node instanceof \DOMDocument) {
198
            $doc = $node;
199
        } else {
200
            $doc = $node->ownerDocument;
201
        }
202
203
        if ($xpCache === null || !$xpCache->document->isSameNode($doc)) {
204
            $xpCache = new \DOMXPath($doc);
205
            $xpCache->registerNamespace('soap-env', Constants::NS_SOAP);
206
            $xpCache->registerNamespace('saml_protocol', Constants::NS_SAMLP);
207
            $xpCache->registerNamespace('saml_assertion', Constants::NS_SAML);
208
            $xpCache->registerNamespace('saml_metadata', Constants::NS_MD);
209
            $xpCache->registerNamespace('ds', XMLSecurityDSig::XMLDSIGNS);
210
            $xpCache->registerNamespace('xenc', XMLSecEnc::XMLENCNS);
211
        }
212
213
        $results = $xpCache->query($query, $node);
214
        $ret = [];
215
        for ($i = 0; $i < $results->length; $i++) {
216
            $ret[$i] = $results->item($i);
217
        }
218
219
        return $ret;
220
    }
221
222
223
    /**
224
     * Make an exact copy the specific \DOMElement.
225
     *
226
     * @param  \DOMElement      $element The element we should copy.
227
     * @param  \DOMElement|null $parent  The target parent element.
228
     * @return \DOMElement      The copied element.
229
     */
230
    public static function copyElement(\DOMElement $element, \DOMElement $parent = null)
231
    {
232
        if ($parent === null) {
233
            $document = DOMDocumentFactory::create();
234
        } else {
235
            $document = $parent->ownerDocument;
236
        }
237
238
        $namespaces = [];
239
        for ($e = $element; $e !== null; $e = $e->parentNode) {
240
            foreach (Utils::xpQuery($e, './namespace::*') as $ns) {
241
                $prefix = $ns->localName;
242
                if ($prefix === 'xml' || $prefix === 'xmlns') {
243
                    continue;
244
                }
245
                $uri = $ns->nodeValue;
246
                if (!isset($namespaces[$prefix])) {
247
                    $namespaces[$prefix] = $uri;
248
                }
249
            }
250
        }
251
252
        /** @var \DOMElement $newElement */
253
        $newElement = $document->importNode($element, true);
254
        if ($parent !== null) {
255
            /* We need to append the child to the parent before we add the namespaces. */
256
            $parent->appendChild($newElement);
257
        }
258
259
        foreach ($namespaces as $prefix => $uri) {
260
            $newElement->setAttributeNS($uri, $prefix.':__ns_workaround__', 'tmp');
261
            $newElement->removeAttributeNS($uri, '__ns_workaround__');
262
        }
263
264
        return $newElement;
265
    }
266
267
268
    /**
269
     * Parse a boolean attribute.
270
     *
271
     * @param  \DOMElement $node          The element we should fetch the attribute from.
272
     * @param  string     $attributeName The name of the attribute.
273
     * @param  mixed      $default       The value that should be returned if the attribute doesn't exist.
274
     * @throws \Exception
275
     * @return bool|mixed The value of the attribute, or $default if the attribute doesn't exist.
276
     */
277
    public static function parseBoolean(\DOMElement $node, $attributeName, $default = null)
278
    {
279
        assert(is_string($attributeName));
280
281
        if (!$node->hasAttribute($attributeName)) {
282
            return $default;
283
        }
284
        $value = $node->getAttribute($attributeName);
285
        switch (strtolower($value)) {
286
            case '0':
287
            case 'false':
288
                return false;
289
            case '1':
290
            case 'true':
291
                return true;
292
            default:
293
                throw new \Exception('Invalid value of boolean attribute '.var_export($attributeName, true).': '.var_export($value, true));
294
        }
295
    }
296
297
298
    /**
299
     * Insert a Signature-node.
300
     *
301
     * @param XMLSecurityKey $key           The key we should use to sign the message.
302
     * @param array          $certificates  The certificates we should add to the signature node.
303
     * @param \DOMElement     $root          The XML node we should sign.
304
     * @param \DOMNode        $insertBefore  The XML element we should insert the signature element before.
305
     * @return void
306
     */
307
    public static function insertSignature(
308
        XMLSecurityKey $key,
309
        array $certificates,
310
        \DOMElement $root,
311
        \DOMNode $insertBefore = null
312
    ) {
313
        $objXMLSecDSig = new XMLSecurityDSig();
314
        $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
315
316
        switch ($key->type) {
317
            case XMLSecurityKey::RSA_SHA256:
318
                $type = XMLSecurityDSig::SHA256;
319
                break;
320
            case XMLSecurityKey::RSA_SHA384:
321
                $type = XMLSecurityDSig::SHA384;
322
                break;
323
            case XMLSecurityKey::RSA_SHA512:
324
                $type = XMLSecurityDSig::SHA512;
325
                break;
326
            default:
327
                $type = XMLSecurityDSig::SHA1;
328
        }
329
330
        $objXMLSecDSig->addReferenceList(
331
            [$root],
332
            $type,
333
            ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N],
334
            ['id_name' => 'ID', 'overwrite' => false]
335
        );
336
337
        $objXMLSecDSig->sign($key);
338
339
        foreach ($certificates as $certificate) {
340
            $objXMLSecDSig->add509Cert($certificate, true);
341
        }
342
343
        $objXMLSecDSig->insertSignature($root, $insertBefore);
344
    }
345
346
347
    /**
348
     * Decrypt an encrypted element.
349
     *
350
     * This is an internal helper function.
351
     *
352
     * @param  \DOMElement     $encryptedData The encrypted data.
353
     * @param  XMLSecurityKey $inputKey      The decryption key.
354
     * @param  array          &$blacklist    Blacklisted decryption algorithms.
355
     * @throws \Exception
356
     * @return \DOMElement     The decrypted element.
357
     */
358
    private static function doDecryptElement(\DOMElement $encryptedData, XMLSecurityKey $inputKey, array &$blacklist)
359
    {
360
        $enc = new XMLSecEnc();
361
362
        $enc->setNode($encryptedData);
363
        $enc->type = $encryptedData->getAttribute("Type");
364
365
        $symmetricKey = $enc->locateKey($encryptedData);
366
        if (!$symmetricKey) {
367
            throw new \Exception('Could not locate key algorithm in encrypted data.');
368
        }
369
370
        $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey);
371
        if (!$symmetricKeyInfo) {
372
            throw new \Exception('Could not locate <dsig:KeyInfo> for the encrypted key.');
373
        }
374
375
        $inputKeyAlgo = $inputKey->getAlgorithm();
376
        if ($symmetricKeyInfo->isEncrypted) {
377
            $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorithm();
378
379
            if (in_array($symKeyInfoAlgo, $blacklist, true)) {
380
                throw new \Exception('Algorithm disabled: '.var_export($symKeyInfoAlgo, true));
381
            }
382
383
            if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) {
384
                /*
385
                 * The RSA key formats are equal, so loading an RSA_1_5 key
386
                 * into an RSA_OAEP_MGF1P key can be done without problems.
387
                 * We therefore pretend that the input key is an
388
                 * RSA_OAEP_MGF1P key.
389
                 */
390
                $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P;
391
            }
392
393
            /* Make sure that the input key format is the same as the one used to encrypt the key. */
394
            if ($inputKeyAlgo !== $symKeyInfoAlgo) {
395
                throw new \Exception(
396
                    'Algorithm mismatch between input key and key used to encrypt '.
397
                    ' the symmetric key for the message. Key was: '.
398
                    var_export($inputKeyAlgo, true).'; message was: '.
399
                    var_export($symKeyInfoAlgo, true)
400
                );
401
            }
402
403
            /** @var XMLSecEnc $encKey */
404
            $encKey = $symmetricKeyInfo->encryptedCtx;
405
            $symmetricKeyInfo->key = $inputKey->key;
406
407
            $keySize = $symmetricKey->getSymmetricKeySize();
408
            if ($keySize === null) {
409
                /* To protect against "key oracle" attacks, we need to be able to create a
410
                 * symmetric key, and for that we need to know the key size.
411
                 */
412
                throw new \Exception('Unknown key size for encryption algorithm: '.var_export($symmetricKey->type, true));
413
            }
414
415
            try {
416
                $key = $encKey->decryptKey($symmetricKeyInfo);
417
                if (strlen($key) != $keySize) {
418
                    throw new \Exception(
419
                        'Unexpected key size ('.strval(strlen($key)*8).'bits) for encryption algorithm: '.
420
                        var_export($symmetricKey->type, true)
421
                    );
422
                }
423
            } catch (\Exception $e) {
424
                /* We failed to decrypt this key. Log it, and substitute a "random" key. */
425
                Utils::getContainer()->getLogger()->error('Failed to decrypt symmetric key: '.$e->getMessage());
426
                /* Create a replacement key, so that it looks like we fail in the same way as if the key was correctly padded. */
427
428
                /* We base the symmetric key on the encrypted key and private key, so that we always behave the
429
                 * same way for a given input key.
430
                 */
431
                $encryptedKey = $encKey->getCipherValue();
432
                $pkey = openssl_pkey_get_details($symmetricKeyInfo->key);
433
                $pkey = sha1(serialize($pkey), true);
434
                $key = sha1($encryptedKey.$pkey, true);
435
436
                /* Make sure that the key has the correct length. */
437
                if (strlen($key) > $keySize) {
438
                    $key = substr($key, 0, $keySize);
439
                } elseif (strlen($key) < $keySize) {
440
                    $key = str_pad($key, $keySize);
441
                }
442
            }
443
            $symmetricKey->loadkey($key);
444
        } else {
445
            $symKeyAlgo = $symmetricKey->getAlgorithm();
446
            /* Make sure that the input key has the correct format. */
447
            if ($inputKeyAlgo !== $symKeyAlgo) {
448
                throw new \Exception(
449
                    'Algorithm mismatch between input key and key in message. '.
450
                    'Key was: '.var_export($inputKeyAlgo, true).'; message was: '.
451
                    var_export($symKeyAlgo, true)
452
                );
453
            }
454
            $symmetricKey = $inputKey;
455
        }
456
457
        $algorithm = $symmetricKey->getAlgorithm();
458
        if (in_array($algorithm, $blacklist, true)) {
459
            throw new \Exception('Algorithm disabled: '.var_export($algorithm, true));
460
        }
461
462
        /** @var string $decrypted */
463
        $decrypted = $enc->decryptNode($symmetricKey, false);
464
465
        /*
466
         * This is a workaround for the case where only a subset of the XML
467
         * tree was serialized for encryption. In that case, we may miss the
468
         * namespaces needed to parse the XML.
469
         */
470
        $xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" '.
471
                        'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.
472
            $decrypted.
473
            '</root>';
474
475
        try {
476
            $newDoc = DOMDocumentFactory::fromString($xml);
477
        } catch (RuntimeException $e) {
478
            throw new \Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?', 0, $e);
479
        }
480
481
        $decryptedElement = $newDoc->firstChild->firstChild;
482
        if ($decryptedElement === null) {
483
            throw new \Exception('Missing encrypted element.');
484
        }
485
486
        if (!($decryptedElement instanceof \DOMElement)) {
487
            throw new \Exception('Decrypted element was not actually a \DOMElement.');
488
        }
489
490
        return $decryptedElement;
491
    }
492
493
494
    /**
495
     * Decrypt an encrypted element.
496
     *
497
     * @param  \DOMElement     $encryptedData The encrypted data.
498
     * @param  XMLSecurityKey $inputKey      The decryption key.
499
     * @param  array          $blacklist     Blacklisted decryption algorithms.
500
     * @throws \Exception
501
     * @return \DOMElement     The decrypted element.
502
     */
503
    public static function decryptElement(\DOMElement $encryptedData, XMLSecurityKey $inputKey, array $blacklist = [])
504
    {
505
        try {
506
            return self::doDecryptElement($encryptedData, $inputKey, $blacklist);
507
        } catch (\Exception $e) {
508
            /*
509
             * Something went wrong during decryption, but for security
510
             * reasons we cannot tell the user what failed.
511
             */
512
            Utils::getContainer()->getLogger()->error('Decryption failed: '.$e->getMessage());
513
            throw new \Exception('Failed to decrypt XML element.', 0, $e);
514
        }
515
    }
516
517
518
    /**
519
     * Extract localized strings from a set of nodes.
520
     *
521
     * @param  \DOMElement $parent       The element that contains the localized strings.
522
     * @param  string     $namespaceURI The namespace URI the localized strings should have.
523
     * @param  string     $localName    The localName of the localized strings.
524
     * @return array      Localized strings.
525
     */
526
    public static function extractLocalizedStrings(\DOMElement $parent, $namespaceURI, $localName)
527
    {
528
        assert(is_string($namespaceURI));
529
        assert(is_string($localName));
530
531
        $ret = [];
532
        for ($node = $parent->firstChild; $node !== null; $node = $node->nextSibling) {
533
            if ($node->namespaceURI !== $namespaceURI || $node->localName !== $localName) {
534
                continue;
535
            }
536
537
            if ($node->hasAttribute('xml:lang')) {
538
                $language = $node->getAttribute('xml:lang');
539
            } else {
540
                $language = 'en';
541
            }
542
            $ret[$language] = trim($node->textContent);
543
        }
544
545
        return $ret;
546
    }
547
548
549
    /**
550
     * Extract strings from a set of nodes.
551
     *
552
     * @param  \DOMElement $parent       The element that contains the localized strings.
553
     * @param  string     $namespaceURI The namespace URI the string elements should have.
554
     * @param  string     $localName    The localName of the string elements.
555
     * @return array      The string values of the various nodes.
556
     */
557
    public static function extractStrings(\DOMElement $parent, $namespaceURI, $localName)
558
    {
559
        assert(is_string($namespaceURI));
560
        assert(is_string($localName));
561
562
        $ret = [];
563
        for ($node = $parent->firstChild; $node !== null; $node = $node->nextSibling) {
564
            if ($node->namespaceURI !== $namespaceURI || $node->localName !== $localName) {
565
                continue;
566
            }
567
            $ret[] = trim($node->textContent);
568
        }
569
570
        return $ret;
571
    }
572
573
574
    /**
575
     * Append string element.
576
     *
577
     * @param  \DOMElement $parent    The parent element we should append the new nodes to.
578
     * @param  string     $namespace The namespace of the created element.
579
     * @param  string     $name      The name of the created element.
580
     * @param  string     $value     The value of the element.
581
     * @return \DOMElement The generated element.
582
     */
583
    public static function addString(\DOMElement $parent, $namespace, $name, $value)
584
    {
585
        assert(is_string($namespace));
586
        assert(is_string($name));
587
        assert(is_string($value));
588
589
        $doc = $parent->ownerDocument;
590
591
        $n = $doc->createElementNS($namespace, $name);
592
        $n->appendChild($doc->createTextNode($value));
593
        $parent->appendChild($n);
594
595
        return $n;
596
    }
597
598
599
    /**
600
     * Append string elements.
601
     *
602
     * @param \DOMElement $parent    The parent element we should append the new nodes to.
603
     * @param string     $namespace The namespace of the created elements
604
     * @param string     $name      The name of the created elements
605
     * @param bool       $localized Whether the strings are localized, and should include the xml:lang attribute.
606
     * @param array      $values    The values we should create the elements from.
607
     * @return void
608
     */
609
    public static function addStrings(\DOMElement $parent, $namespace, $name, $localized, array $values)
610
    {
611
        assert(is_string($namespace));
612
        assert(is_string($name));
613
        assert(is_bool($localized));
614
615
        $doc = $parent->ownerDocument;
616
617
        foreach ($values as $index => $value) {
618
            $n = $doc->createElementNS($namespace, $name);
619
            $n->appendChild($doc->createTextNode($value));
620
            if ($localized) {
621
                $n->setAttribute('xml:lang', $index);
622
            }
623
            $parent->appendChild($n);
624
        }
625
    }
626
627
628
    /**
629
     * Create a KeyDescriptor with the given certificate.
630
     *
631
     * @param  string                     $x509Data The certificate, as a base64-encoded DER data.
632
     * @return \SAML2\XML\md\KeyDescriptor The keydescriptor.
633
     */
634
    public static function createKeyDescriptor($x509Data)
635
    {
636
        assert(is_string($x509Data));
637
638
        $x509Certificate = new X509Certificate();
639
        $x509Certificate->setCertificate($x509Data);
640
641
        $x509Data = new X509Data();
642
        $x509Data->addData($x509Certificate);
643
644
        $keyInfo = new KeyInfo();
645
        $keyInfo->addInfo($x509Data);
646
647
        $keyDescriptor = new KeyDescriptor();
648
        $keyDescriptor->KeyInfo = $keyInfo;
649
650
        return $keyDescriptor;
651
    }
652
653
654
    /**
655
     * This function converts a SAML2 timestamp on the form
656
     * yyyy-mm-ddThh:mm:ss(\.s+)?Z to a UNIX timestamp. The sub-second
657
     * part is ignored.
658
     *
659
     * Andreas comments:
660
     *  I got this timestamp from Shibboleth 1.3 IdP: 2008-01-17T11:28:03.577Z
661
     *  Therefore I added to possibility to have microseconds to the format.
662
     * Added: (\.\\d{1,3})? to the regex.
663
     *
664
     * Note that we always require a 'Z' timezone for the dateTime to be valid.
665
     * This is not in the SAML spec but that's considered to be a bug in the
666
     * spec. See https://github.com/simplesamlphp/saml2/pull/36 for some
667
     * background.
668
     *
669
     * @param string $time The time we should convert.
670
     * @throws \Exception
671
     * @return int Converted to a unix timestamp.
672
     */
673
    public static function xsDateTimeToTimestamp($time)
674
    {
675
        $matches = [];
676
677
        // We use a very strict regex to parse the timestamp.
678
        $regex = '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d{1,9})?Z$/D';
679
        if (preg_match($regex, $time, $matches) == 0) {
680
            throw new \Exception(
681
                'Invalid SAML2 timestamp passed to xsDateTimeToTimestamp: '.$time
682
            );
683
        }
684
685
        // Extract the different components of the time from the  matches in the regex.
686
        // intval will ignore leading zeroes in the string.
687
        $year   = intval($matches[1]);
688
        $month  = intval($matches[2]);
689
        $day    = intval($matches[3]);
690
        $hour   = intval($matches[4]);
691
        $minute = intval($matches[5]);
692
        $second = intval($matches[6]);
693
694
        // We use gmmktime because the timestamp will always be given
695
        //in UTC.
696
        $ts = gmmktime($hour, $minute, $second, $month, $day, $year);
697
698
        return $ts;
699
    }
700
701
702
    /**
703
     * @return \SAML2\Compat\Ssp\Container
704
     */
705
    public static function getContainer()
706
    {
707
        return ContainerSingleton::getInstance();
708
    }
709
}
710