Passed
Push — develop ( c90c5c...8a861b )
by Remco
04:41
created

XMLSecurityDSig::addKeyInfoAndName()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 18
nc 10
nop 2
dl 0
loc 27
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * xmlseclibs.php
5
 *
6
 * Copyright (c) 2007-2010, Robert Richards <[email protected]>.
7
 * All rights reserved.
8
 *
9
 * Redistribution and use in source and binary forms, with or without
10
 * modification, are permitted provided that the following conditions
11
 * are met:
12
 *
13
 *   * Redistributions of source code must retain the above copyright
14
 *     notice, this list of conditions and the following disclaimer.
15
 *
16
 *   * Redistributions in binary form must reproduce the above copyright
17
 *     notice, this list of conditions and the following disclaimer in
18
 *     the documentation and/or other materials provided with the
19
 *     distribution.
20
 *
21
 *   * Neither the name of Robert Richards nor the names of his
22
 *     contributors may be used to endorse or promote products derived
23
 *     from this software without specific prior written permission.
24
 *
25
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36
 * POSSIBILITY OF SUCH DAMAGE.
37
 *
38
 * @author     Robert Richards <[email protected]>
39
 * @copyright  2007-2011 Robert Richards <[email protected]>
40
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @version    1.3.0
42
 */
43
/*
44
  Functions to generate simple cases of Exclusive Canonical XML - Callable function is C14NGeneral()
45
  i.e.: $canonical = C14NGeneral($domelement, TRUE);
46
 */
47
48
/* helper function */
49
function sortAndAddAttrs($element, $arAtts) {
50
    $newAtts = array();
51
    foreach ($arAtts AS $attnode) {
52
        $newAtts[$attnode->nodeName] = $attnode;
53
    }
54
    ksort($newAtts);
55
    foreach ($newAtts as $attnode) {
56
        $element->setAttribute($attnode->nodeName, $attnode->nodeValue);
57
    }
58
}
59
60
/* helper function */
61
62
function canonical($tree, $element, $withcomments) {
63
    if ($tree->nodeType != XML_DOCUMENT_NODE) {
64
        $dom = $tree->ownerDocument;
65
    } else {
66
        $dom = $tree;
67
    }
68
    if ($element->nodeType != XML_ELEMENT_NODE) {
69
        if ($element->nodeType == XML_DOCUMENT_NODE) {
70
            foreach ($element->childNodes AS $node) {
71
                canonical($dom, $node, $withcomments);
72
            }
73
            return;
74
        }
75
        if ($element->nodeType == XML_COMMENT_NODE && !$withcomments) {
76
            return;
77
        }
78
        $tree->appendChild($dom->importNode($element, TRUE));
79
        return;
80
    }
81
    $arNS = array();
82
    if ($element->namespaceURI != "") {
83
        if ($element->prefix == "") {
84
            $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
85
        } else {
86
            $prefix = $tree->lookupPrefix($element->namespaceURI);
87
            if ($prefix == $element->prefix) {
88
                $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
89
            } else {
90
                $elCopy = $dom->createElement($element->nodeName);
91
                $arNS[$element->namespaceURI] = $element->prefix;
92
            }
93
        }
94
    } else {
95
        $elCopy = $dom->createElement($element->nodeName);
96
    }
97
    $tree->appendChild($elCopy);
98
99
    /* Create DOMXPath based on original document */
100
    $xPath = new DOMXPath($element->ownerDocument);
101
102
    /* Get namespaced attributes */
103
    $arAtts = $xPath->query('attribute::*[namespace-uri(.) != ""]', $element);
104
105
    /* Create an array with namespace URIs as keys, and sort them */
106
    foreach ($arAtts AS $attnode) {
107
        if (array_key_exists($attnode->namespaceURI, $arNS) &&
108
                ($arNS[$attnode->namespaceURI] == $attnode->prefix)) {
109
            continue;
110
        }
111
        $prefix = $tree->lookupPrefix($attnode->namespaceURI);
112
        if ($prefix != $attnode->prefix) {
113
            $arNS[$attnode->namespaceURI] = $attnode->prefix;
114
        } else {
115
            $arNS[$attnode->namespaceURI] = NULL;
116
        }
117
    }
118
    if (count($arNS) > 0) {
119
        asort($arNS);
120
    }
121
122
    /* Add namespace nodes */
123
    foreach ($arNS AS $namespaceURI => $prefix) {
124
        if ($prefix != NULL) {
125
            $elCopy->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" . $prefix, $namespaceURI);
126
        }
127
    }
128
    if (count($arNS) > 0) {
129
        ksort($arNS);
130
    }
131
132
    /* Get attributes not in a namespace, and then sort and add them */
133
    $arAtts = $xPath->query('attribute::*[namespace-uri(.) = ""]', $element);
134
    sortAndAddAttrs($elCopy, $arAtts);
135
136
    /* Loop through the URIs, and then sort and add attributes within that namespace */
137
    foreach ($arNS as $nsURI => $prefix) {
138
        $arAtts = $xPath->query('attribute::*[namespace-uri(.) = "' . $nsURI . '"]', $element);
139
        sortAndAddAttrs($elCopy, $arAtts);
140
    }
141
142
    foreach ($element->childNodes AS $node) {
143
        canonical($elCopy, $node, $withcomments);
144
    }
145
}
146
147
/*
148
  $element - DOMElement for which to produce the canonical version of
149
  $exclusive - boolean to indicate exclusive canonicalization (must pass TRUE)
150
  $withcomments - boolean indicating wether or not to include comments in canonicalized form
151
 */
152
153
function C14NGeneral($element, $exclusive=FALSE, $withcomments=FALSE) {
154
    /* IF PHP 5.2+ then use built in canonical functionality */
155
    $php_version = explode('.', PHP_VERSION);
156
    if (($php_version[0] > 5) || ($php_version[0] == 5 && $php_version[1] >= 2)) {
157
        return $element->C14N($exclusive, $withcomments);
158
    }
159
160
    /* Must be element or document */
161
    if (!$element instanceof DOMElement && !$element instanceof DOMDocument) {
162
        return NULL;
163
    }
164
    /* Currently only exclusive XML is supported */
165
    if ($exclusive == FALSE) {
166
        throw new Exception("Only exclusive canonicalization is supported in this version of PHP");
167
    }
168
169
    $copyDoc = new DOMDocument();
170
    canonical($copyDoc, $element, $withcomments);
171
    return $copyDoc->saveXML($copyDoc->documentElement, LIBXML_NOEMPTYTAG);
172
}
173
174
class XMLSecurityKey {
175
    const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
176
    const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
177
    const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
178
    const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
179
    const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
180
    const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
181
    const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
182
    const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
183
    const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
184
185
    private $cryptParams = array();
186
    public $type = 0;
187
    public $key = NULL;
188
    public $passphrase = "";
189
    public $iv = NULL;
190
    public $name = NULL;
191
    public $keyChain = NULL;
192
    public $isEncrypted = FALSE;
193
    public $encryptedCtx = NULL;
194
    public $guid = NULL;
195
196
    /**
197
     * This variable contains the certificate as a string if this key represents an X509-certificate.
198
     * If this key doesn't represent a certificate, this will be NULL.
199
     */
200
    private $x509Certificate = NULL;
201
202
    /* This variable contains the certificate thunbprint if we have loaded an X509-certificate. */
203
    private $X509Thumbprint = NULL;
204
205
    public function __construct($type, $params=NULL) {
206
        srand();
207
        switch ($type) {
208
            case (XMLSecurityKey::TRIPLEDES_CBC):
209
                $this->cryptParams['library'] = 'mcrypt';
210
                $this->cryptParams['cipher'] = MCRYPT_TRIPLEDES;
211
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
212
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
213
                $this->cryptParams['keysize'] = 24;
214
                break;
215
            case (XMLSecurityKey::AES128_CBC):
216
                $this->cryptParams['library'] = 'mcrypt';
217
                $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
218
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
219
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
220
                $this->cryptParams['keysize'] = 16;
221
                break;
222
            case (XMLSecurityKey::AES192_CBC):
223
                $this->cryptParams['library'] = 'mcrypt';
224
                $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
225
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
226
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
227
                $this->cryptParams['keysize'] = 24;
228
                break;
229
            case (XMLSecurityKey::AES256_CBC):
230
                $this->cryptParams['library'] = 'mcrypt';
231
                $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
232
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
233
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
234
                $this->cryptParams['keysize'] = 32;
235
                break;
236
            case (XMLSecurityKey::RSA_1_5):
237
                $this->cryptParams['library'] = 'openssl';
238
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
239
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
240
                if (is_array($params) && !empty($params['type'])) {
241
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
242
                        $this->cryptParams['type'] = $params['type'];
243
                        break;
244
                    }
245
                }
246
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
247
                return;
248
            case (XMLSecurityKey::RSA_OAEP_MGF1P):
249
                $this->cryptParams['library'] = 'openssl';
250
                $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
251
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
252
                $this->cryptParams['hash'] = NULL;
253
                if (is_array($params) && !empty($params['type'])) {
254
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
255
                        $this->cryptParams['type'] = $params['type'];
256
                        break;
257
                    }
258
                }
259
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
260
                return;
261
            case (XMLSecurityKey::RSA_SHA1):
262
                $this->cryptParams['library'] = 'openssl';
263
                $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
264
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
265
                if (is_array($params) && !empty($params['type'])) {
266
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
267
                        $this->cryptParams['type'] = $params['type'];
268
                        break;
269
                    }
270
                }
271
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
272
                break;
273
            case (XMLSecurityKey::RSA_SHA256):
274
                $this->cryptParams['library'] = 'openssl';
275
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
276
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
277
                $this->cryptParams['digest'] = 'SHA256';
278
                if (is_array($params) && !empty($params['type'])) {
279
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
280
                        $this->cryptParams['type'] = $params['type'];
281
                        break;
282
                    }
283
                }
284
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
285
                break;
286
            default:
287
                throw new Exception('Invalid Key Type');
288
                return;
289
        }
290
        $this->type = $type;
291
    }
292
293
    /**
294
     * Retrieve the key size for the symmetric encryption algorithm..
295
     *
296
     * If the key size is unknown, or this isn't a symmetric encryption algorithm,
297
     * NULL is returned.
298
     *
299
     * @return int|NULL  The number of bytes in the key.
300
     */
301
    public function getSymmetricKeySize() {
302
        if (!isset($this->cryptParams['keysize'])) {
303
            return NULL;
304
        }
305
        return $this->cryptParams['keysize'];
306
    }
307
308
    public function generateSessionKey() {
309
        if (!isset($this->cryptParams['keysize'])) {
310
            throw new Exception('Unknown key size for type "' . $this->type . '".');
311
        }
312
        $keysize = $this->cryptParams['keysize'];
313
314
        if (function_exists('openssl_random_pseudo_bytes')) {
315
            /* We have PHP >= 5.3 - use openssl to generate session key. */
316
            $key = openssl_random_pseudo_bytes($keysize);
317
        } else {
318
            /* Generating random key using iv generation routines */
319
            $key = mcrypt_create_iv($keysize, MCRYPT_RAND);
320
        }
321
322
        if ($this->type === XMLSecurityKey::TRIPLEDES_CBC) {
323
            /* Make sure that the generated key has the proper parity bits set.
324
             * Mcrypt doesn't care about the parity bits, but others may care.
325
             */
326
            for ($i = 0; $i < strlen($key); $i++) {
327
                $byte = ord($key[$i]) & 0xfe;
328
                $parity = 1;
329
                for ($j = 1; $j < 8; $j++) {
330
                    $parity ^= ($byte >> $j) & 1;
331
                }
332
                $byte |= $parity;
333
                $key[$i] = chr($byte);
334
            }
335
        }
336
337
        $this->key = $key;
338
        return $key;
339
    }
340
341
    public static function getRawThumbprint($cert) {
342
343
        $arCert = explode("\n", $cert);
344
        $data = '';
345
        $inData = FALSE;
346
347
        foreach ($arCert AS $curData) {
348
            if (!$inData) {
349
                if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
350
                    $inData = TRUE;
351
                }
352
            } else {
353
                if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
354
                    $inData = FALSE;
355
                    break;
356
                }
357
                $data .= trim($curData);
358
            }
359
        }
360
361
        if (!empty($data)) {
362
            return strtolower(sha1(base64_decode($data)));
363
        }
364
365
        return NULL;
366
    }
367
368
    public function loadKey($key, $isFile=FALSE, $isCert = FALSE) {
369
        if ($isFile) {
370
            $this->key = file_get_contents($key);
371
        } else {
372
            $this->key = $key;
373
        }
374
        if ($isCert) {
375
            $this->key = openssl_x509_read($this->key);
376
            openssl_x509_export($this->key, $str_cert);
377
            $this->x509Certificate = $str_cert;
378
            $this->key = $str_cert;
379
        } else {
380
            $this->x509Certificate = NULL;
381
        }
382
        if ($this->cryptParams['library'] == 'openssl') {
383
            if ($this->cryptParams['type'] == 'public') {
384
                if ($isCert) {
385
                    /* Load the thumbprint if this is an X509 certificate. */
386
                    $this->X509Thumbprint = self::getRawThumbprint($this->key);
387
                }
388
                $this->key = openssl_get_publickey($this->key);
389
            } else {
390
                $this->key = openssl_get_privatekey($this->key, $this->passphrase);
391
            }
392
        } else if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
393
            /* Check key length */
394
            switch ($this->type) {
395
                case (XMLSecurityKey::AES256_CBC):
396
                    if (strlen($this->key) < 25) {
397
                        throw new Exception('Key must contain at least 25 characters for this cipher');
398
                    }
399
                    break;
400
                case (XMLSecurityKey::AES192_CBC):
401
                    if (strlen($this->key) < 17) {
402
                        throw new Exception('Key must contain at least 17 characters for this cipher');
403
                    }
404
                    break;
405
            }
406
        }
407
    }
408
409
    private function encryptMcrypt($data) {
410
        $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
411
        $this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
412
        mcrypt_generic_init($td, $this->key, $this->iv);
413
        if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
414
            $bs = mcrypt_enc_get_block_size($td);
415
            for ($datalen0 = $datalen = strlen($data); (($datalen % $bs) != ($bs - 1)); $datalen++)
416
                $data.=chr(rand(1, 127));
417
            $data.=chr($datalen - $datalen0 + 1);
418
        }
419
        $encrypted_data = $this->iv . mcrypt_generic($td, $data);
420
        mcrypt_generic_deinit($td);
421
        mcrypt_module_close($td);
422
        return $encrypted_data;
423
    }
424
425
    private function decryptMcrypt($data) {
426
        $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
427
        $iv_length = mcrypt_enc_get_iv_size($td);
428
429
        $this->iv = substr($data, 0, $iv_length);
430
        $data = substr($data, $iv_length);
431
432
        mcrypt_generic_init($td, $this->key, $this->iv);
433
        $decrypted_data = mdecrypt_generic($td, $data);
434
        mcrypt_generic_deinit($td);
435
        mcrypt_module_close($td);
436
        if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
437
            $dataLen = strlen($decrypted_data);
438
            $paddingLength = substr($decrypted_data, $dataLen - 1, 1);
439
            $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
440
        }
441
        return $decrypted_data;
442
    }
443
444
    private function encryptOpenSSL($data) {
445
        if ($this->cryptParams['type'] == 'public') {
446
            if (!openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
447
                throw new Exception('Failure encrypting Data');
448
                return;
449
            }
450
        } else {
451
            if (!openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
452
                throw new Exception('Failure encrypting Data');
453
                return;
454
            }
455
        }
456
        return $encrypted_data;
457
    }
458
459
    private function decryptOpenSSL($data) {
460
        if ($this->cryptParams['type'] == 'public') {
461
            if (!openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
462
                throw new Exception('Failure decrypting Data');
463
                return;
464
            }
465
        } else {
466
            if (!openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
467
                throw new Exception('Failure decrypting Data');
468
                return;
469
            }
470
        }
471
        return $decrypted;
472
    }
473
474
    private function signOpenSSL($data) {
475
        $algo = OPENSSL_ALGO_SHA1;
476
        if (!empty($this->cryptParams['digest'])) {
477
            $algo = $this->cryptParams['digest'];
478
        }
479
        if (!openssl_sign($data, $signature, $this->key, $algo)) {
480
            throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
481
            return;
482
        }
483
        return $signature;
484
    }
485
486
    private function verifyOpenSSL($data, $signature) {
487
        $algo = OPENSSL_ALGO_SHA1;
488
        if (!empty($this->cryptParams['digest'])) {
489
            $algo = $this->cryptParams['digest'];
490
        }
491
        return openssl_verify($data, $signature, $this->key, $algo);
492
    }
493
494
    public function encryptData($data) {
495
        switch ($this->cryptParams['library']) {
496
            case 'mcrypt':
497
                return $this->encryptMcrypt($data);
498
                break;
499
            case 'openssl':
500
                return $this->encryptOpenSSL($data);
501
                break;
502
        }
503
    }
504
505
    public function decryptData($data) {
506
        switch ($this->cryptParams['library']) {
507
            case 'mcrypt':
508
                return $this->decryptMcrypt($data);
509
                break;
510
            case 'openssl':
511
                return $this->decryptOpenSSL($data);
512
                break;
513
        }
514
    }
515
516
    public function signData($data) {
517
        switch ($this->cryptParams['library']) {
518
            case 'openssl':
519
                return $this->signOpenSSL($data);
520
                break;
521
        }
522
    }
523
524
    public function verifySignature($data, $signature) {
525
        switch ($this->cryptParams['library']) {
526
            case 'openssl':
527
                return $this->verifyOpenSSL($data, $signature);
528
                break;
529
        }
530
    }
531
532
    public function getAlgorith() {
533
        return $this->cryptParams['method'];
534
    }
535
536
    static function makeAsnSegment($type, $string) {
537
        switch ($type) {
538
            case 0x02:
539
                if (ord($string) > 0x7f)
540
                    $string = chr(0) . $string;
541
                break;
542
            case 0x03:
543
                $string = chr(0) . $string;
544
                break;
545
        }
546
547
        $length = strlen($string);
548
549
        if ($length < 128) {
550
            $output = sprintf("%c%c%s", $type, $length, $string);
551
        } else if ($length < 0x0100) {
552
            $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
553
        } else if ($length < 0x010000) {
554
            $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
555
        } else {
556
            $output = NULL;
557
        }
558
        return($output);
559
    }
560
561
    /* Modulus and Exponent must already be base64 decoded */
562
563
    static function convertRSA($modulus, $exponent) {
564
        /* make an ASN publicKeyInfo */
565
        $exponentEncoding = XMLSecurityKey::makeAsnSegment(0x02, $exponent);
566
        $modulusEncoding = XMLSecurityKey::makeAsnSegment(0x02, $modulus);
567
        $sequenceEncoding = XMLSecurityKey:: makeAsnSegment(0x30, $modulusEncoding . $exponentEncoding);
568
        $bitstringEncoding = XMLSecurityKey::makeAsnSegment(0x03, $sequenceEncoding);
569
        $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
570
        $publicKeyInfo = XMLSecurityKey::makeAsnSegment(0x30, $rsaAlgorithmIdentifier . $bitstringEncoding);
571
572
        /* encode the publicKeyInfo in base64 and add PEM brackets */
573
        $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
574
        $encoding = "-----BEGIN PUBLIC KEY-----\n";
575
        $offset = 0;
576
        while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
577
            $encoding = $encoding . $segment . "\n";
578
            $offset += 64;
579
        }
580
        return $encoding . "-----END PUBLIC KEY-----\n";
581
    }
582
583
    public function serializeKey($parent) {
584
585
    }
586
587
    /**
588
     * Retrieve the X509 certificate this key represents.
589
     *
590
     * Will return the X509 certificate in PEM-format if this key represents
591
     * an X509 certificate.
592
     *
593
     * @return  The X509 certificate or NULL if this key doesn't represent an X509-certificate.
594
     */
595
    public function getX509Certificate() {
596
        return $this->x509Certificate;
597
    }
598
599
    /* Get the thumbprint of this X509 certificate.
600
     *
601
     * Returns:
602
     *  The thumbprint as a lowercase 40-character hexadecimal number, or NULL
603
     *  if this isn't a X509 certificate.
604
     */
605
606
    public function getX509Thumbprint() {
607
        return $this->X509Thumbprint;
608
    }
609
610
    /**
611
     * Create key from an EncryptedKey-element.
612
     *
613
     * @param DOMElement $element  The EncryptedKey-element.
614
     * @return XMLSecurityKey  The new key.
615
     */
616
    public static function fromEncryptedKeyElement(DOMElement $element) {
617
618
        $objenc = new XMLSecEnc();
619
        $objenc->setNode($element);
620
        if (!$objKey = $objenc->locateKey()) {
621
            throw new Exception("Unable to locate algorithm for this Encrypted Key");
622
        }
623
        $objKey->isEncrypted = TRUE;
624
        $objKey->encryptedCtx = $objenc;
625
        XMLSecEnc::staticLocateKeyInfo($objKey, $element);
626
        return $objKey;
627
    }
628
629
}
630
631
class XMLSecurityDSig {
632
    const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
633
    const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
634
    const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
635
    const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512';
636
    const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160';
637
638
    const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
639
    const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
640
    const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#';
641
    const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
642
643
    const template = '<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
644
  <SignedInfo>
645
    <SignatureMethod />
646
  </SignedInfo>
647
</Signature>';
648
649
    public $sigNode = NULL;
650
    public $idKeys = array();
651
    public $idNS = array();
652
    private $signedInfo = NULL;
653
    private $xPathCtx = NULL;
654
    private $canonicalMethod = NULL;
655
    private $prefix = NULL;
656
    private $searchpfx = 'secdsig';
657
658
    /* This variable contains an associative array of validated nodes. */
659
    private $validatedNodes = NULL;
660
661
    public function __construct() {
662
        $sigdoc = new DOMDocument();
663
        $sigdoc->loadXML(XMLSecurityDSig::template);
664
        $this->sigNode = $sigdoc->documentElement;
665
    }
666
667
    private function resetXPathObj() {
668
        $this->xPathCtx = NULL;
669
    }
670
671
    private function getXPathObj() {
672
        if (empty($this->xPathCtx) && !empty($this->sigNode)) {
673
            $xpath = new DOMXPath($this->sigNode->ownerDocument);
674
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
675
            $this->xPathCtx = $xpath;
676
        }
677
        return $this->xPathCtx;
678
    }
679
680
    static function generate_GUID($prefix='pfx') {
681
        $uuid = md5(uniqid(rand(), true));
682
        $guid = $prefix . substr($uuid, 0, 8) . "-" .
683
                substr($uuid, 8, 4) . "-" .
684
                substr($uuid, 12, 4) . "-" .
685
                substr($uuid, 16, 4) . "-" .
686
                substr($uuid, 20, 12);
687
        return $guid;
688
    }
689
690
    public function locateSignature($objDoc) {
691
        if ($objDoc instanceof DOMDocument) {
692
            $doc = $objDoc;
693
        } else {
694
            $doc = $objDoc->ownerDocument;
695
        }
696
        if ($doc) {
697
            $xpath = new DOMXPath($doc);
698
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
699
            $query = ".//secdsig:Signature";
700
            $nodeset = $xpath->query($query, $objDoc);
701
            $this->sigNode = $nodeset->item(0);
702
            return $this->sigNode;
703
        }
704
        return NULL;
705
    }
706
707
    public function createNewSignNode($name, $value=NULL) {
708
        $doc = $this->sigNode->ownerDocument;
709
        if ($this->prefix != null) {
710
            $name = $this->prefix . ':' . $name;
711
        }
712
        if (!is_null($value)) {
713
            $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $name, $value);
714
        } else {
715
            $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $name);
716
        }
717
        return $node;
718
    }
719
720
    public function setCanonicalMethod($method) {
721
        switch ($method) {
722
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
723
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
724
            case 'http://www.w3.org/2001/10/xml-exc-c14n#':
725
            case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
726
                $this->canonicalMethod = $method;
727
                break;
728
            default:
729
                throw new Exception('Invalid Canonical Method');
730
        }
731
        if ($xpath = $this->getXPathObj()) {
732
            $query = './' . $this->searchpfx . ':SignedInfo';
733
            $nodeset = $xpath->query($query, $this->sigNode);
734
            if ($sinfo = $nodeset->item(0)) {
735
                $query = './' . $this->searchpfx . 'CanonicalizationMethod';
736
                $nodeset = $xpath->query($query, $sinfo);
737
                if (!($canonNode = $nodeset->item(0))) {
738
                    $canonNode = $this->createNewSignNode('CanonicalizationMethod');
739
                    $sinfo->insertBefore($canonNode, $sinfo->firstChild);
740
                }
741
                $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
742
            }
743
        }
744
    }
745
746
    private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
747
        $exclusive = FALSE;
748
        $withComments = FALSE;
749
        switch ($canonicalmethod) {
750
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
751
                $exclusive = FALSE;
752
                $withComments = FALSE;
753
                break;
754
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
755
                $withComments = TRUE;
756
                break;
757
            case 'http://www.w3.org/2001/10/xml-exc-c14n#':
758
                $exclusive = TRUE;
759
                break;
760
            case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
761
                $exclusive = TRUE;
762
                $withComments = TRUE;
763
                break;
764
        }
765
        /* Support PHP versions < 5.2 not containing C14N methods in DOM extension */
766
        $php_version = explode('.', PHP_VERSION);
767
        if (($php_version[0] < 5) || ($php_version[0] == 5 && $php_version[1] < 2)) {
768
            if (!is_null($arXPath)) {
769
                throw new Exception("PHP 5.2.0 or higher is required to perform XPath Transformations");
770
            }
771
            return C14NGeneral($node, $exclusive, $withComments);
772
        }
773
        return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
774
    }
775
776
    public function canonicalizeSignedInfo() {
777
778
        $doc = $this->sigNode->ownerDocument;
779
        $canonicalmethod = NULL;
780
        if ($doc) {
781
            $xpath = $this->getXPathObj();
782
            $query = "./secdsig:SignedInfo";
783
            $nodeset = $xpath->query($query, $this->sigNode);
784
            if ($signInfoNode = $nodeset->item(0)) {
785
                $query = "./secdsig:CanonicalizationMethod";
786
                $nodeset = $xpath->query($query, $signInfoNode);
787
                if ($canonNode = $nodeset->item(0)) {
788
                    $canonicalmethod = $canonNode->getAttribute('Algorithm');
789
                }
790
                $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
791
                return $this->signedInfo;
792
            }
793
        }
794
        return NULL;
795
    }
796
797
    public function calculateDigest($digestAlgorithm, $data) {
798
        switch ($digestAlgorithm) {
799
            case XMLSecurityDSig::SHA1:
800
                $alg = 'sha1';
801
                break;
802
            case XMLSecurityDSig::SHA256:
803
                $alg = 'sha256';
804
                break;
805
            case XMLSecurityDSig::SHA512:
806
                $alg = 'sha512';
807
                break;
808
            case XMLSecurityDSig::RIPEMD160:
809
                $alg = 'ripemd160';
810
                break;
811
            default:
812
                throw new Exception("Cannot validate digest: Unsupported Algorith <$digestAlgorithm>");
813
        }
814
        if (function_exists('hash')) {
815
            return base64_encode(hash($alg, $data, TRUE));
816
        } elseif (function_exists('mhash')) {
817
            $alg = "MHASH_" . strtoupper($alg);
818
            return base64_encode(mhash(constant($alg), $data));
819
        } elseif ($alg === 'sha1') {
820
            return base64_encode(sha1($data, TRUE));
821
        } else {
822
            throw new Exception('xmlseclibs is unable to calculate a digest. Maybe you need the mhash library?');
823
        }
824
    }
825
826
    public function validateDigest($refNode, $data) {
827
        $xpath = new DOMXPath($refNode->ownerDocument);
828
        $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
829
        $query = 'string(./secdsig:DigestMethod/@Algorithm)';
830
        $digestAlgorithm = $xpath->evaluate($query, $refNode);
831
        $digValue = $this->calculateDigest($digestAlgorithm, $data);
832
        $query = 'string(./secdsig:DigestValue)';
833
        $digestValue = $xpath->evaluate($query, $refNode);
834
        return ($digValue == $digestValue);
835
    }
836
837
    public function processTransforms($refNode, $objData, $includeCommentNodes = TRUE) {
838
        $data = $objData;
839
        $xpath = new DOMXPath($refNode->ownerDocument);
840
        $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
841
        $query = './secdsig:Transforms/secdsig:Transform';
842
        $nodelist = $xpath->query($query, $refNode);
843
        $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
844
        $arXPath = NULL;
845
        $prefixList = NULL;
846
        foreach ($nodelist AS $transform) {
847
            $algorithm = $transform->getAttribute("Algorithm");
848
            switch ($algorithm) {
849
                case 'http://www.w3.org/2001/10/xml-exc-c14n#':
850
                case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
851
852
                    if (!$includeCommentNodes) {
853
                        /* We remove comment nodes by forcing it to use a canonicalization
854
                         * without comments.
855
                         */
856
                        $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#';
857
                    } else {
858
                        $canonicalMethod = $algorithm;
859
                    }
860
861
                    $node = $transform->firstChild;
862
                    while ($node) {
863
                        if ($node->localName == 'InclusiveNamespaces') {
864
                            if ($pfx = $node->getAttribute('PrefixList')) {
865
                                $arpfx = array();
866
                                $pfxlist = explode(" ", $pfx);
867
                                foreach ($pfxlist AS $pfx) {
868
                                    $val = trim($pfx);
869
                                    if (!empty($val)) {
870
                                        $arpfx[] = $val;
871
                                    }
872
                                }
873
                                if (count($arpfx) > 0) {
874
                                    $prefixList = $arpfx;
875
                                }
876
                            }
877
                            break;
878
                        }
879
                        $node = $node->nextSibling;
880
                    }
881
                    break;
882
                case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
883
                case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
884
                    if (!$includeCommentNodes) {
885
                        /* We remove comment nodes by forcing it to use a canonicalization
886
                         * without comments.
887
                         */
888
                        $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
889
                    } else {
890
                        $canonicalMethod = $algorithm;
891
                    }
892
893
                    break;
894
                case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
895
                    $node = $transform->firstChild;
896
                    while ($node) {
897
                        if ($node->localName == 'XPath') {
898
                            $arXPath = array();
899
                            $arXPath['query'] = '(.//. | .//@* | .//namespace::*)[' . $node->nodeValue . ']';
900
                            $arXpath['namespaces'] = array();
901
                            $nslist = $xpath->query('./namespace::*', $node);
902
                            foreach ($nslist AS $nsnode) {
903
                                if ($nsnode->localName != "xml") {
904
                                    $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue;
905
                                }
906
                            }
907
                            break;
908
                        }
909
                        $node = $node->nextSibling;
910
                    }
911
                    break;
912
            }
913
        }
914
        if ($data instanceof DOMNode) {
915
            $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList);
916
        }
917
        return $data;
918
    }
919
920
    public function processRefNode($refNode) {
921
        $dataObject = NULL;
922
923
        /*
924
         * Depending on the URI, we may not want to include comments in the result
925
         * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel
926
         */
927
        $includeCommentNodes = TRUE;
928
929
        if ($uri = $refNode->getAttribute("URI")) {
930
            $arUrl = parse_url($uri);
931
            if (empty($arUrl['path'])) {
932
                if ($identifier = $arUrl['fragment']) {
933
934
                    /* This reference identifies a node with the given id by using
935
                     * a URI on the form "#identifier". This should not include comments.
936
                     */
937
                    $includeCommentNodes = FALSE;
938
939
                    $xPath = new DOMXPath($refNode->ownerDocument);
940
                    if ($this->idNS && is_array($this->idNS)) {
941
                        foreach ($this->idNS AS $nspf => $ns) {
942
                            $xPath->registerNamespace($nspf, $ns);
943
                        }
944
                    }
945
                    $iDlist = '@Id="' . $identifier . '"';
946
                    if (is_array($this->idKeys)) {
947
                        foreach ($this->idKeys AS $idKey) {
948
                            $iDlist .= " or @$idKey='$identifier'";
949
                        }
950
                    }
951
                    $query = '//*[' . $iDlist . ']';
952
                    $dataObject = $xPath->query($query)->item(0);
953
                } else {
954
                    $dataObject = $refNode->ownerDocument;
955
                }
956
            } else {
957
                $dataObject = file_get_contents($arUrl);
958
            }
959
        } else {
960
            /* This reference identifies the root node with an empty URI. This should
961
             * not include comments.
962
             */
963
            $includeCommentNodes = FALSE;
964
965
            $dataObject = $refNode->ownerDocument;
966
        }
967
        $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes);
968
        if (!$this->validateDigest($refNode, $data)) {
969
            return FALSE;
970
        }
971
972
        if ($dataObject instanceof DOMNode) {
973
            /* Add this node to the list of validated nodes. */
974
            if (!empty($identifier)) {
975
                $this->validatedNodes[$identifier] = $dataObject;
976
            } else {
977
                $this->validatedNodes[] = $dataObject;
978
            }
979
        }
980
981
        return TRUE;
982
    }
983
984
    public function getRefNodeID($refNode) {
985
        if ($uri = $refNode->getAttribute("URI")) {
986
            $arUrl = parse_url($uri);
987
            if (empty($arUrl['path'])) {
988
                if ($identifier = $arUrl['fragment']) {
989
                    return $identifier;
990
                }
991
            }
992
        }
993
        return null;
994
    }
995
996
    public function getRefIDs() {
997
        $refids = array();
998
        $doc = $this->sigNode->ownerDocument;
999
1000
        $xpath = $this->getXPathObj();
1001
        $query = "./secdsig:SignedInfo/secdsig:Reference";
1002
        $nodeset = $xpath->query($query, $this->sigNode);
1003
        if ($nodeset->length == 0) {
1004
            throw new Exception("Reference nodes not found");
1005
        }
1006
        foreach ($nodeset AS $refNode) {
1007
            $refids[] = $this->getRefNodeID($refNode);
1008
        }
1009
        return $refids;
1010
    }
1011
1012
    public function validateReference() {
1013
        $doc = $this->sigNode->ownerDocument;
1014
        if (!$doc->isSameNode($this->sigNode)) {
1015
            $this->sigNode->parentNode->removeChild($this->sigNode);
1016
        }
1017
        $xpath = $this->getXPathObj();
1018
        $query = "./secdsig:SignedInfo/secdsig:Reference";
1019
        $nodeset = $xpath->query($query, $this->sigNode);
1020
        if ($nodeset->length == 0) {
1021
            throw new Exception("Reference nodes not found");
1022
        }
1023
1024
        /* Initialize/reset the list of validated nodes. */
1025
        $this->validatedNodes = array();
1026
1027
        foreach ($nodeset AS $refNode) {
1028
            if (!$this->processRefNode($refNode)) {
1029
                /* Clear the list of validated nodes. */
1030
                $this->validatedNodes = NULL;
1031
                throw new Exception("Reference validation failed");
1032
            }
1033
        }
1034
        return TRUE;
1035
    }
1036
1037
    private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=NULL, $options=NULL) {
1038
        $prefix = NULL;
1039
        $prefix_ns = NULL;
1040
        $id_name = 'Id';
1041
        $overwrite_id = TRUE;
1042
        $force_uri = FALSE;
1043
1044
        if (is_array($options)) {
1045
            $prefix = empty($options['prefix']) ? NULL : $options['prefix'];
1046
            $prefix_ns = empty($options['prefix_ns']) ? NULL : $options['prefix_ns'];
1047
            $id_name = empty($options['id_name']) ? 'Id' : $options['id_name'];
1048
            $overwrite_id = !isset($options['overwrite']) ? TRUE : (bool) $options['overwrite'];
1049
            $force_uri = !isset($options['force_uri']) ? FALSE : (bool) $options['force_uri'];
1050
        }
1051
1052
        $attname = $id_name;
1053
        if (!empty($prefix)) {
1054
            $attname = $prefix . ':' . $attname;
1055
        }
1056
1057
        $refNode = $this->createNewSignNode('Reference');
1058
        $sinfoNode->appendChild($refNode);
1059
1060
        if (!$node instanceof DOMDocument) {
1061
            $uri = NULL;
1062
            if (!$overwrite_id) {
1063
                $uri = $node->getAttributeNS($prefix_ns, $attname);
1064
            }
1065
            if (empty($uri)) {
1066
                $uri = XMLSecurityDSig::generate_GUID();
1067
                $node->setAttributeNS($prefix_ns, $attname, $uri);
1068
            }
1069
            $refNode->setAttribute("URI", '#' . $uri);
1070
        } elseif ($force_uri) {
1071
            $refNode->setAttribute("URI", '');
1072
        }
1073
1074
        $transNodes = $this->createNewSignNode('Transforms');
1075
        $refNode->appendChild($transNodes);
1076
1077
        if (is_array($arTransforms)) {
1078
            foreach ($arTransforms AS $transform) {
1079
                $transNode = $this->createNewSignNode('Transform');
1080
                $transNodes->appendChild($transNode);
1081
                if (is_array($transform) &&
1082
                        (!empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
1083
                        (!empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
1084
                    $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
1085
                    $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
1086
                    $transNode->appendChild($XPathNode);
1087
                    if (!empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) {
1088
                        foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) {
1089
                            $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace);
1090
                        }
1091
                    }
1092
                } else {
1093
                    $transNode->setAttribute('Algorithm', $transform);
1094
                }
1095
            }
1096
        } elseif (!empty($this->canonicalMethod)) {
1097
            $transNode = $this->createNewSignNode('Transform');
1098
            $transNodes->appendChild($transNode);
1099
            $transNode->setAttribute('Algorithm', $this->canonicalMethod);
1100
        }
1101
1102
        $canonicalData = $this->processTransforms($refNode, $node);
1103
        $digValue = $this->calculateDigest($algorithm, $canonicalData);
1104
1105
        $digestMethod = $this->createNewSignNode('DigestMethod');
1106
        $refNode->appendChild($digestMethod);
1107
        $digestMethod->setAttribute('Algorithm', $algorithm);
1108
1109
        $digestValue = $this->createNewSignNode('DigestValue', $digValue);
1110
        $refNode->appendChild($digestValue);
1111
    }
1112
1113
    public function addReference($node, $algorithm, $arTransforms=NULL, $options=NULL) {
1114
        if ($xpath = $this->getXPathObj()) {
1115
            $query = "./secdsig:SignedInfo";
1116
            $nodeset = $xpath->query($query, $this->sigNode);
1117
            if ($sInfo = $nodeset->item(0)) {
1118
                $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
1119
            }
1120
        }
1121
    }
1122
1123
    public function addReferenceList($arNodes, $algorithm, $arTransforms=NULL, $options=NULL) {
1124
        if ($xpath = $this->getXPathObj()) {
1125
            $query = "./secdsig:SignedInfo";
1126
            $nodeset = $xpath->query($query, $this->sigNode);
1127
            if ($sInfo = $nodeset->item(0)) {
1128
                foreach ($arNodes AS $node) {
1129
                    $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
1130
                }
1131
            }
1132
        }
1133
    }
1134
1135
    public function addObject($data, $mimetype=NULL, $encoding=NULL) {
1136
        $objNode = $this->createNewSignNode('Object');
1137
        $this->sigNode->appendChild($objNode);
1138
        if (!empty($mimetype)) {
1139
            $objNode->setAtribute('MimeType', $mimetype);
1140
        }
1141
        if (!empty($encoding)) {
1142
            $objNode->setAttribute('Encoding', $encoding);
1143
        }
1144
1145
        if ($data instanceof DOMElement) {
1146
            $newData = $this->sigNode->ownerDocument->importNode($data, TRUE);
1147
        } else {
1148
            $newData = $this->sigNode->ownerDocument->createTextNode($data);
1149
        }
1150
        $objNode->appendChild($newData);
1151
1152
        return $objNode;
1153
    }
1154
1155
    public function locateKey($node=NULL) {
1156
        if (empty($node)) {
1157
            $node = $this->sigNode;
1158
        }
1159
        if (!$node instanceof DOMNode) {
1160
            return NULL;
1161
        }
1162
        if ($doc = $node->ownerDocument) {
1163
            $xpath = new DOMXPath($doc);
1164
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1165
            $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)";
1166
            $algorithm = $xpath->evaluate($query, $node);
1167
            if ($algorithm) {
1168
                try {
1169
                    $objKey = new XMLSecurityKey($algorithm, array('type' => 'public'));
1170
                } catch (Exception $e) {
1171
                    return NULL;
1172
                }
1173
                return $objKey;
1174
            }
1175
        }
1176
        return NULL;
1177
    }
1178
1179
    public function verify($objKey) {
1180
        $doc = $this->sigNode->ownerDocument;
1181
        $xpath = new DOMXPath($doc);
1182
        $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1183
        $query = "string(./secdsig:SignatureValue)";
1184
        $sigValue = $xpath->evaluate($query, $this->sigNode);
1185
        if (empty($sigValue)) {
1186
            throw new Exception("Unable to locate SignatureValue");
1187
        }
1188
        return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
1189
    }
1190
1191
    public function signData($objKey, $data) {
1192
        return $objKey->signData($data);
1193
    }
1194
1195
    public function sign($objKey, $appendToNode = NULL) {
1196
        // If we have a parent node append it now so C14N properly works
1197
        if ($appendToNode != NULL) {
1198
            $this->resetXPathObj();
1199
            $this->appendSignature($appendToNode);
1200
            $this->sigNode = $appendToNode->lastChild;
1201
        }
1202
        if ($xpath = $this->getXPathObj()) {
1203
            $query = "./secdsig:SignedInfo";
1204
            $nodeset = $xpath->query($query, $this->sigNode);
1205
            if ($sInfo = $nodeset->item(0)) {
1206
                $query = "./secdsig:SignatureMethod";
1207
                $nodeset = $xpath->query($query, $sInfo);
1208
                $sMethod = $nodeset->item(0);
1209
                $sMethod->setAttribute('Algorithm', $objKey->type);
1210
                $data = $this->canonicalizeData($sInfo, $this->canonicalMethod);
1211
                $sigValue = base64_encode($this->signData($objKey, $data));
1212
                $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
1213
                if ($infoSibling = $sInfo->nextSibling) {
1214
                    $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
1215
                } else {
1216
                    $this->sigNode->appendChild($sigValueNode);
1217
                }
1218
            }
1219
        }
1220
    }
1221
1222
    public function appendCert() {
1223
1224
    }
1225
1226
    public function appendKey($objKey, $parent=NULL) {
1227
        $objKey->serializeKey($parent);
1228
    }
1229
1230
    /**
1231
     * This function inserts the signature element.
1232
     *
1233
     * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode
1234
     * is specified, the signature element will be inserted as the last element before $beforeNode.
1235
     *
1236
     * @param $node  The node the signature element should be inserted into.
1237
     * @param $beforeNode  The node the signature element should be located before.
1238
     *
1239
     * @return DOMNode The signature element node
1240
     */
1241
    public function insertSignature($node, $beforeNode = NULL) {
1242
1243
        $document = $node->ownerDocument;
1244
        $signatureElement = $document->importNode($this->sigNode, TRUE);
1245
1246
        if ($beforeNode == NULL) {
1247
            return $node->insertBefore($signatureElement);
1248
        } else {
1249
            return $node->insertBefore($signatureElement, $beforeNode);
1250
        }
1251
    }
1252
1253
    public function appendSignature($parentNode, $insertBefore = FALSE) {
1254
        $beforeNode = $insertBefore ? $parentNode->firstChild : NULL;
1255
        return $this->insertSignature($parentNode, $beforeNode);
1256
    }
1257
1258
    static function get509XCert($cert, $isPEMFormat=TRUE) {
1259
        $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1260
        if (!empty($certs)) {
1261
            return $certs[0];
1262
        }
1263
        return '';
1264
    }
1265
1266
    static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
1267
        if ($isPEMFormat) {
1268
            $data = '';
1269
            $certlist = array();
1270
            $arCert = explode("\n", $certs);
1271
            $inData = FALSE;
1272
            foreach ($arCert AS $curData) {
1273
                if (!$inData) {
1274
                    if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
1275
                        $inData = TRUE;
1276
                    }
1277
                } else {
1278
                    if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
1279
                        $inData = FALSE;
1280
                        $certlist[] = $data;
1281
                        $data = '';
1282
                        continue;
1283
                    }
1284
                    $data .= trim($curData);
1285
                }
1286
            }
1287
            return $certlist;
1288
        } else {
1289
            return array($certs);
1290
        }
1291
    }
1292
1293
    static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL) {
1294
        if ($isURL) {
1295
            $cert = file_get_contents($cert);
1296
        }
1297
        if (!$parentRef instanceof DOMElement) {
1298
            throw new Exception('Invalid parent Node parameter');
1299
        }
1300
        $baseDoc = $parentRef->ownerDocument;
1301
1302
        if (empty($xpath)) {
1303
            $xpath = new DOMXPath($parentRef->ownerDocument);
1304
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1305
        }
1306
1307
        $query = "./secdsig:KeyInfo";
1308
        $nodeset = $xpath->query($query, $parentRef);
1309
        $keyInfo = $nodeset->item(0);
1310
        if (!$keyInfo) {
1311
            $inserted = FALSE;
1312
            $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo');
1313
1314
            $query = "./secdsig:Object";
1315
            $nodeset = $xpath->query($query, $parentRef);
1316
            if ($sObject = $nodeset->item(0)) {
1317
                $sObject->parentNode->insertBefore($keyInfo, $sObject);
1318
                $inserted = TRUE;
1319
            }
1320
1321
            if (!$inserted) {
1322
                $parentRef->appendChild($keyInfo);
1323
            }
1324
        }
1325
1326
        // Add all certs if there are more than one
1327
        $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1328
1329
        // Atach X509 data node
1330
        $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data');
1331
        $keyInfo->appendChild($x509DataNode);
1332
1333
        // Atach all certificate nodes
1334
        foreach ($certs as $X509Cert) {
1335
            $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
1336
            $x509DataNode->appendChild($x509CertNode);
1337
        }
1338
    }
1339
1340
    public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False) {
1341
        if ($xpath = $this->getXPathObj()) {
1342
            self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath);
1343
        }
1344
    }
1345
1346
    function addKeyInfoAndName($keyName, $xpath=NULL) {
1347
1348
        $baseDoc = $this->sigNode->ownerDocument;
1349
1350
        if (empty($xpath)) {
1351
            $xpath = new DOMXPath($baseDoc);
1352
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1353
        }
1354
1355
        $query = "./secdsig:KeyInfo";
1356
        $nodeset = $xpath->query($query, $this->sigNode);
1357
        $keyInfo = $nodeset->item(0);
1358
        if (!$keyInfo) {
1359
            $inserted = FALSE;
1360
            $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'KeyInfo');
1361
1362
            $query = "./secdsig:Object";
1363
            $nodeset = $xpath->query($query, $this->sigNode);
1364
            if ($sObject = $nodeset->item(0)) {
1365
                $sObject->parentNode->insertBefore($keyInfo, $sObject);
1366
                $inserted = TRUE;
1367
            }
1368
            if (!$inserted) {
1369
                $this->sigNode->appendChild($keyInfo);
1370
            }
1371
        }
1372
        $keyInfo->appendChild($baseDoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'KeyName', $keyName));
1373
    }
1374
1375
    /* This function retrieves an associative array of the validated nodes.
1376
     *
1377
     * The array will contain the id of the referenced node as the key and the node itself
1378
     * as the value.
1379
     *
1380
     * Returns:
1381
     *  An associative array of validated nodes or NULL if no nodes have been validated.
1382
     */
1383
1384
    public function getValidatedNodes() {
1385
        return $this->validatedNodes;
1386
    }
1387
1388
}
1389
1390
class XMLSecEnc {
1391
    const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
1392
   <xenc:CipherData>
1393
      <xenc:CipherValue></xenc:CipherValue>
1394
   </xenc:CipherData>
1395
</xenc:EncryptedData>";
1396
1397
    const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
1398
    const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
1399
    const URI = 3;
1400
    const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
1401
1402
    private $encdoc = NULL;
1403
    private $rawNode = NULL;
1404
    public $type = NULL;
1405
    public $encKey = NULL;
1406
    private $references = array();
1407
1408
    public function __construct() {
1409
        $this->_resetTemplate();
1410
    }
1411
1412
    private function _resetTemplate() {
1413
        $this->encdoc = new DOMDocument();
1414
        $this->encdoc->loadXML(XMLSecEnc::template);
1415
    }
1416
1417
    public function addReference($name, $node, $type) {
1418
        if (!$node instanceOf DOMNode) {
1419
            throw new Exception('$node is not of type DOMNode');
1420
        }
1421
        $curencdoc = $this->encdoc;
1422
        $this->_resetTemplate();
1423
        $encdoc = $this->encdoc;
1424
        $this->encdoc = $curencdoc;
1425
        $refuri = XMLSecurityDSig::generate_GUID();
1426
        $element = $encdoc->documentElement;
1427
        $element->setAttribute("Id", $refuri);
1428
        $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri);
1429
    }
1430
1431
    public function setNode($node) {
1432
        $this->rawNode = $node;
1433
    }
1434
1435
    public function encryptNode($objKey, $replace=TRUE) {
1436
        $data = '';
1437
        if (empty($this->rawNode)) {
1438
            throw new Exception('Node to encrypt has not been set');
1439
        }
1440
        if (!$objKey instanceof XMLSecurityKey) {
1441
            throw new Exception('Invalid Key');
1442
        }
1443
        $doc = $this->rawNode->ownerDocument;
1444
        $xPath = new DOMXPath($this->encdoc);
1445
        $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
1446
        $cipherValue = $objList->item(0);
1447
        if ($cipherValue == NULL) {
1448
            throw new Exception('Error locating CipherValue element within template');
1449
        }
1450
        switch ($this->type) {
1451
            case (XMLSecEnc::Element):
1452
                $data = $doc->saveXML($this->rawNode);
1453
                $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element);
1454
                break;
1455
            case (XMLSecEnc::Content):
1456
                $children = $this->rawNode->childNodes;
1457
                foreach ($children AS $child) {
1458
                    $data .= $doc->saveXML($child);
1459
                }
1460
                $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content);
1461
                break;
1462
            default:
1463
                throw new Exception('Type is currently not supported');
1464
                return;
1465
        }
1466
1467
        $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1468
        $encMethod->setAttribute('Algorithm', $objKey->getAlgorith());
1469
        $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild);
1470
1471
        $strEncrypt = base64_encode($objKey->encryptData($data));
1472
        $value = $this->encdoc->createTextNode($strEncrypt);
1473
        $cipherValue->appendChild($value);
1474
1475
        if ($replace) {
1476
            switch ($this->type) {
1477
                case (XMLSecEnc::Element):
1478
                    if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1479
                        return $this->encdoc;
1480
                    }
1481
                    $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1482
                    $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1483
                    return $importEnc;
1484
                    break;
1485
                case (XMLSecEnc::Content):
1486
                    $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1487
                    while ($this->rawNode->firstChild) {
1488
                        $this->rawNode->removeChild($this->rawNode->firstChild);
1489
                    }
1490
                    $this->rawNode->appendChild($importEnc);
1491
                    return $importEnc;
1492
                    break;
1493
            }
1494
        }
1495
    }
1496
1497
    public function encryptReferences($objKey) {
1498
        $curRawNode = $this->rawNode;
1499
        $curType = $this->type;
1500
        foreach ($this->references AS $name => $reference) {
1501
            $this->encdoc = $reference["encnode"];
1502
            $this->rawNode = $reference["node"];
1503
            $this->type = $reference["type"];
1504
            try {
1505
                $encNode = $this->encryptNode($objKey);
1506
                $this->references[$name]["encnode"] = $encNode;
1507
            } catch (Exception $e) {
1508
                $this->rawNode = $curRawNode;
1509
                $this->type = $curType;
1510
                throw $e;
1511
            }
1512
        }
1513
        $this->rawNode = $curRawNode;
1514
        $this->type = $curType;
1515
    }
1516
1517
    /**
1518
     * Retrieve the CipherValue text from this encrypted node.
1519
     *
1520
     * @return string|NULL  The Ciphervalue text, or NULL if no CipherValue is found.
1521
     */
1522
    public function getCipherValue() {
1523
        if (empty($this->rawNode)) {
1524
            throw new Exception('Node to decrypt has not been set');
1525
        }
1526
1527
        $doc = $this->rawNode->ownerDocument;
1528
        $xPath = new DOMXPath($doc);
1529
        $xPath->registerNamespace('xmlencr', XMLSecEnc::XMLENCNS);
1530
        /* Only handles embedded content right now and not a reference */
1531
        $query = "./xmlencr:CipherData/xmlencr:CipherValue";
1532
        $nodeset = $xPath->query($query, $this->rawNode);
1533
        $node = $nodeset->item(0);
1534
1535
        if (!$node) {
1536
            return NULL;
1537
        }
1538
1539
        return base64_decode($node->nodeValue);
1540
    }
1541
1542
    /**
1543
     * Decrypt this encrypted node.
1544
     *
1545
     * The behaviour of this function depends on the value of $replace.
1546
     * If $replace is FALSE, we will return the decrypted data as a string.
1547
     * If $replace is TRUE, we will insert the decrypted element(s) into the
1548
     * document, and return the decrypted element(s).
1549
     *
1550
     * @params XMLSecurityKey $objKey  The decryption key that should be used when decrypting the node.
1551
     * @params boolean $replace  Whether we should replace the encrypted node in the XML document with the decrypted data. The default is TRUE.
1552
     * @return string|DOMElement  The decrypted data.
1553
     */
1554
    public function decryptNode($objKey, $replace=TRUE) {
1555
        if (!$objKey instanceof XMLSecurityKey) {
1556
            throw new Exception('Invalid Key');
1557
        }
1558
1559
        $encryptedData = $this->getCipherValue();
1560
        if ($encryptedData) {
1561
            $decrypted = $objKey->decryptData($encryptedData);
1562
            if ($replace) {
1563
                switch ($this->type) {
1564
                    case (XMLSecEnc::Element):
1565
                        $newdoc = new DOMDocument();
1566
                        $newdoc->loadXML($decrypted);
1567
                        if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1568
                            return $newdoc;
1569
                        }
1570
                        $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE);
1571
                        $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1572
                        return $importEnc;
1573
                        break;
1574
                    case (XMLSecEnc::Content):
1575
                        if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1576
                            $doc = $this->rawNode;
1577
                        } else {
1578
                            $doc = $this->rawNode->ownerDocument;
1579
                        }
1580
                        $newFrag = $doc->createDocumentFragment();
1581
                        $newFrag->appendXML($decrypted);
1582
                        $parent = $this->rawNode->parentNode;
1583
                        $parent->replaceChild($newFrag, $this->rawNode);
1584
                        return $parent;
1585
                        break;
1586
                    default:
1587
                        return $decrypted;
1588
                }
1589
            } else {
1590
                return $decrypted;
1591
            }
1592
        } else {
1593
            throw new Exception("Cannot locate encrypted data");
1594
        }
1595
    }
1596
1597
    public function encryptKey($srcKey, $rawKey, $append=TRUE) {
1598
        if ((!$srcKey instanceof XMLSecurityKey) || (!$rawKey instanceof XMLSecurityKey)) {
1599
            throw new Exception('Invalid Key');
1600
        }
1601
        $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
1602
        $root = $this->encdoc->documentElement;
1603
        $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey');
1604
        if ($append) {
1605
            $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild);
1606
            $keyInfo->appendChild($encKey);
1607
        } else {
1608
            $this->encKey = $encKey;
1609
        }
1610
        $encMethod = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1611
        $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
1612
        if (!empty($srcKey->name)) {
1613
            $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1614
            $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
1615
        }
1616
        $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData'));
1617
        $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey));
1618
        if (is_array($this->references) && count($this->references) > 0) {
1619
            $refList = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:ReferenceList'));
1620
            foreach ($this->references AS $name => $reference) {
1621
                $refuri = $reference["refuri"];
1622
                $dataRef = $refList->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:DataReference'));
1623
                $dataRef->setAttribute("URI", '#' . $refuri);
1624
            }
1625
        }
1626
        return;
1627
    }
1628
1629
    public function decryptKey($encKey) {
1630
        if (!$encKey->isEncrypted) {
1631
            throw new Exception("Key is not Encrypted");
1632
        }
1633
        if (empty($encKey->key)) {
1634
            throw new Exception("Key is missing data to perform the decryption");
1635
        }
1636
        return $this->decryptNode($encKey, FALSE);
1637
    }
1638
1639
    public function locateEncryptedData($element) {
1640
        if ($element instanceof DOMDocument) {
1641
            $doc = $element;
1642
        } else {
1643
            $doc = $element->ownerDocument;
1644
        }
1645
        if ($doc) {
1646
            $xpath = new DOMXPath($doc);
1647
            $query = "//*[local-name()='EncryptedData' and namespace-uri()='" . XMLSecEnc::XMLENCNS . "']";
1648
            $nodeset = $xpath->query($query);
1649
            return $nodeset->item(0);
1650
        }
1651
        return NULL;
1652
    }
1653
1654
    public function locateKey($node=NULL) {
1655
        if (empty($node)) {
1656
            $node = $this->rawNode;
1657
        }
1658
        if (!$node instanceof DOMNode) {
1659
            return NULL;
1660
        }
1661
        if ($doc = $node->ownerDocument) {
1662
            $xpath = new DOMXPath($doc);
1663
            $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1664
            $query = ".//xmlsecenc:EncryptionMethod";
1665
            $nodeset = $xpath->query($query, $node);
1666
            if ($encmeth = $nodeset->item(0)) {
1667
                $attrAlgorithm = $encmeth->getAttribute("Algorithm");
1668
                try {
1669
                    $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private'));
1670
                } catch (Exception $e) {
1671
                    return NULL;
1672
                }
1673
                return $objKey;
1674
            }
1675
        }
1676
        return NULL;
1677
    }
1678
1679
    static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) {
1680
        if (empty($node) || (!$node instanceof DOMNode)) {
1681
            return NULL;
1682
        }
1683
        $doc = $node->ownerDocument;
1684
        if (!$doc) {
1685
            return NULL;
1686
        }
1687
1688
        $xpath = new DOMXPath($doc);
1689
        $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1690
        $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS);
1691
        $query = "./xmlsecdsig:KeyInfo";
1692
        $nodeset = $xpath->query($query, $node);
1693
        $encmeth = $nodeset->item(0);
1694
        if (!$encmeth) {
1695
            /* No KeyInfo in EncryptedData / EncryptedKey. */
1696
            return $objBaseKey;
1697
        }
1698
1699
        foreach ($encmeth->childNodes AS $child) {
1700
            switch ($child->localName) {
1701
                case 'KeyName':
1702
                    if (!empty($objBaseKey)) {
1703
                        $objBaseKey->name = $child->nodeValue;
1704
                    }
1705
                    break;
1706
                case 'KeyValue':
1707
                    foreach ($child->childNodes AS $keyval) {
1708
                        switch ($keyval->localName) {
1709
                            case 'DSAKeyValue':
1710
                                throw new Exception("DSAKeyValue currently not supported");
1711
                                break;
1712
                            case 'RSAKeyValue':
1713
                                $modulus = NULL;
1714
                                $exponent = NULL;
1715
                                if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
1716
                                    $modulus = base64_decode($modulusNode->nodeValue);
1717
                                }
1718
                                if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
1719
                                    $exponent = base64_decode($exponentNode->nodeValue);
1720
                                }
1721
                                if (empty($modulus) || empty($exponent)) {
1722
                                    throw new Exception("Missing Modulus or Exponent");
1723
                                }
1724
                                $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
1725
                                $objBaseKey->loadKey($publicKey);
1726
                                break;
1727
                        }
1728
                    }
1729
                    break;
1730
                case 'RetrievalMethod':
1731
                    $type = $child->getAttribute('Type');
1732
                    if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') {
1733
                        /* Unsupported key type. */
1734
                        break;
1735
                    }
1736
                    $uri = $child->getAttribute('URI');
1737
                    if ($uri[0] !== '#') {
1738
                        /* URI not a reference - unsupported. */
1739
                        break;
1740
                    }
1741
                    $id = substr($uri, 1);
1742
1743
                    $query = "//xmlsecenc:EncryptedKey[@Id='$id']";
1744
                    $keyElement = $xpath->query($query)->item(0);
1745
                    if (!$keyElement) {
1746
                        throw new Exception("Unable to locate EncryptedKey with @Id='$id'.");
1747
                    }
1748
1749
                    return XMLSecurityKey::fromEncryptedKeyElement($keyElement);
1750
                case 'EncryptedKey':
1751
                    return XMLSecurityKey::fromEncryptedKeyElement($child);
1752
                case 'X509Data':
1753
                    if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
1754
                        if ($x509certNodes->length > 0) {
1755
                            $x509cert = $x509certNodes->item(0)->textContent;
1756
                            $x509cert = str_replace(array("\r", "\n"), "", $x509cert);
1757
                            $x509cert = "-----BEGIN CERTIFICATE-----\n" . chunk_split($x509cert, 64, "\n") . "-----END CERTIFICATE-----\n";
1758
                            $objBaseKey->loadKey($x509cert, FALSE, TRUE);
1759
                        }
1760
                    }
1761
                    break;
1762
            }
1763
        }
1764
        return $objBaseKey;
1765
    }
1766
1767
    public function locateKeyInfo($objBaseKey=NULL, $node=NULL) {
1768
        if (empty($node)) {
1769
            $node = $this->rawNode;
1770
        }
1771
        return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node);
1772
    }
1773
1774
}
1775