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

XMLSecurityKey::custom_openssl_sign()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 4
dl 0
loc 11
rs 9.4285
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
    function custom_openssl_sign($data, &$signature, $priv_key_id, $signature_alg = 'sha256WithRSAEncryption') {
475
        $pinfo = openssl_pkey_get_details($priv_key_id);
476
        $hash = hash('sha256', $data);
477
        $t = '3031300d060960864801650304020105000420'; # sha256
478
        $t .= $hash;
479
        $pslen = $pinfo['bits']/8 - (strlen($t)/2 + 3);
480
481
        $eb = '0001' . str_repeat('FF', $pslen) . '00' . $t;
482
        $eb = pack('H*', $eb);
483
484
        return openssl_private_encrypt($eb, $signature, $priv_key_id, OPENSSL_NO_PADDING);
485
    }
486
487
    function custom_openssl_verify ($data, &$signature, $priv_key_id, $signature_alg = 'sha256WithRSAEncryption') {
488
        $pinfo = openssl_pkey_get_details($priv_key_id);
489
        $hash = hash('sha256', $data);
490
        $t = '3031300d060960864801650304020105000420'; # sha256
491
        $t .= $hash;
492
        $pslen = $pinfo['bits']/8 - (strlen($t)/2 + 3);
493
494
        $eb = '0001' . str_repeat('FF', $pslen) . '00' . $t;
495
        $eb = pack('H*', $eb);
496
497
        return openssl_public_decrypt($eb, $signature, $priv_key_id, OPENSSL_NO_PADDING);
498
    }
499
500
    private function signOpenSSL($data) {
501
        $algo = OPENSSL_ALGO_SHA1;
502
        if (!empty($this->cryptParams['digest'])) {
503
            $algo = $this->cryptParams['digest'];
504
        }
505
        $signature = '';
506
507
        if (!$this->custom_openssl_sign($data, $signature, $this->key)) {
508
            throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
509
            return;
510
        }
511
        return $signature;
512
    }
513
514
    private function verifyOpenSSL($data, $signature) {
515
        $algo = OPENSSL_ALGO_SHA1;
516
        if (!empty($this->cryptParams['digest'])) {
517
            $algo = $this->cryptParams['digest'];
518
        }
519
        return $this->custom_openssl_verify($data, $signature, $this->key);
520
    }
521
522
    public function encryptData($data) {
523
        switch ($this->cryptParams['library']) {
524
            case 'mcrypt':
525
                return $this->encryptMcrypt($data);
526
                break;
527
            case 'openssl':
528
                return $this->encryptOpenSSL($data);
529
                break;
530
        }
531
    }
532
533
    public function decryptData($data) {
534
        switch ($this->cryptParams['library']) {
535
            case 'mcrypt':
536
                return $this->decryptMcrypt($data);
537
                break;
538
            case 'openssl':
539
                return $this->decryptOpenSSL($data);
540
                break;
541
        }
542
    }
543
544
    public function signData($data) {
545
        switch ($this->cryptParams['library']) {
546
            case 'openssl':
547
                return $this->signOpenSSL($data);
548
                break;
549
        }
550
    }
551
552
    public function verifySignature($data, $signature) {
553
        switch ($this->cryptParams['library']) {
554
            case 'openssl':
555
                return $this->verifyOpenSSL($data, $signature);
556
                break;
557
        }
558
    }
559
560
    public function getAlgorith() {
561
        return $this->cryptParams['method'];
562
    }
563
564
    static function makeAsnSegment($type, $string) {
565
        switch ($type) {
566
            case 0x02:
567
                if (ord($string) > 0x7f)
568
                    $string = chr(0) . $string;
569
                break;
570
            case 0x03:
571
                $string = chr(0) . $string;
572
                break;
573
        }
574
575
        $length = strlen($string);
576
577
        if ($length < 128) {
578
            $output = sprintf("%c%c%s", $type, $length, $string);
579
        } else if ($length < 0x0100) {
580
            $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
581
        } else if ($length < 0x010000) {
582
            $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
583
        } else {
584
            $output = NULL;
585
        }
586
        return($output);
587
    }
588
589
    /* Modulus and Exponent must already be base64 decoded */
590
591
    static function convertRSA($modulus, $exponent) {
592
        /* make an ASN publicKeyInfo */
593
        $exponentEncoding = XMLSecurityKey::makeAsnSegment(0x02, $exponent);
594
        $modulusEncoding = XMLSecurityKey::makeAsnSegment(0x02, $modulus);
595
        $sequenceEncoding = XMLSecurityKey:: makeAsnSegment(0x30, $modulusEncoding . $exponentEncoding);
596
        $bitstringEncoding = XMLSecurityKey::makeAsnSegment(0x03, $sequenceEncoding);
597
        $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
598
        $publicKeyInfo = XMLSecurityKey::makeAsnSegment(0x30, $rsaAlgorithmIdentifier . $bitstringEncoding);
599
600
        /* encode the publicKeyInfo in base64 and add PEM brackets */
601
        $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
602
        $encoding = "-----BEGIN PUBLIC KEY-----\n";
603
        $offset = 0;
604
        while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
605
            $encoding = $encoding . $segment . "\n";
606
            $offset += 64;
607
        }
608
        return $encoding . "-----END PUBLIC KEY-----\n";
609
    }
610
611
    public function serializeKey($parent) {
612
613
    }
614
615
    /**
616
     * Retrieve the X509 certificate this key represents.
617
     *
618
     * Will return the X509 certificate in PEM-format if this key represents
619
     * an X509 certificate.
620
     *
621
     * @return  The X509 certificate or NULL if this key doesn't represent an X509-certificate.
622
     */
623
    public function getX509Certificate() {
624
        return $this->x509Certificate;
625
    }
626
627
    /* Get the thumbprint of this X509 certificate.
628
     *
629
     * Returns:
630
     *  The thumbprint as a lowercase 40-character hexadecimal number, or NULL
631
     *  if this isn't a X509 certificate.
632
     */
633
634
    public function getX509Thumbprint() {
635
        return $this->X509Thumbprint;
636
    }
637
638
    /**
639
     * Create key from an EncryptedKey-element.
640
     *
641
     * @param DOMElement $element  The EncryptedKey-element.
642
     * @return XMLSecurityKey  The new key.
643
     */
644
    public static function fromEncryptedKeyElement(DOMElement $element) {
645
646
        $objenc = new XMLSecEnc();
647
        $objenc->setNode($element);
648
        if (!$objKey = $objenc->locateKey()) {
649
            throw new Exception("Unable to locate algorithm for this Encrypted Key");
650
        }
651
        $objKey->isEncrypted = TRUE;
652
        $objKey->encryptedCtx = $objenc;
653
        XMLSecEnc::staticLocateKeyInfo($objKey, $element);
654
        return $objKey;
655
    }
656
657
}
658
659
class XMLSecurityDSig {
660
    const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
661
    const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
662
    const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
663
    const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512';
664
    const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160';
665
666
    const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
667
    const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
668
    const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#';
669
    const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
670
671
    const template = '<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
672
  <SignedInfo>
673
    <SignatureMethod />
674
  </SignedInfo>
675
</Signature>';
676
677
    public $sigNode = NULL;
678
    public $idKeys = array();
679
    public $idNS = array();
680
    private $signedInfo = NULL;
681
    private $xPathCtx = NULL;
682
    private $canonicalMethod = NULL;
683
    private $prefix = NULL;
684
    private $searchpfx = 'secdsig';
685
686
    /* This variable contains an associative array of validated nodes. */
687
    private $validatedNodes = NULL;
688
689
    public function __construct() {
690
        $sigdoc = new DOMDocument();
691
        $sigdoc->loadXML(XMLSecurityDSig::template);
692
        $this->sigNode = $sigdoc->documentElement;
693
    }
694
695
    private function resetXPathObj() {
696
        $this->xPathCtx = NULL;
697
    }
698
699
    private function getXPathObj() {
700
        if (empty($this->xPathCtx) && !empty($this->sigNode)) {
701
            $xpath = new DOMXPath($this->sigNode->ownerDocument);
702
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
703
            $this->xPathCtx = $xpath;
704
        }
705
        return $this->xPathCtx;
706
    }
707
708
    static function generate_GUID($prefix='pfx') {
709
        $uuid = md5(uniqid(rand(), true));
710
        $guid = $prefix . substr($uuid, 0, 8) . "-" .
711
            substr($uuid, 8, 4) . "-" .
712
            substr($uuid, 12, 4) . "-" .
713
            substr($uuid, 16, 4) . "-" .
714
            substr($uuid, 20, 12);
715
        return $guid;
716
    }
717
718
    public function locateSignature($objDoc) {
719
        if ($objDoc instanceof DOMDocument) {
720
            $doc = $objDoc;
721
        } else {
722
            $doc = $objDoc->ownerDocument;
723
        }
724
        if ($doc) {
725
            $xpath = new DOMXPath($doc);
726
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
727
            $query = ".//secdsig:Signature";
728
            $nodeset = $xpath->query($query, $objDoc);
729
            $this->sigNode = $nodeset->item(0);
730
            return $this->sigNode;
731
        }
732
        return NULL;
733
    }
734
735
    public function createNewSignNode($name, $value=NULL) {
736
        $doc = $this->sigNode->ownerDocument;
737
        if ($this->prefix != null) {
738
            $name = $this->prefix . ':' . $name;
739
        }
740
        if (!is_null($value)) {
741
            $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $name, $value);
742
        } else {
743
            $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $name);
744
        }
745
        return $node;
746
    }
747
748
    public function setCanonicalMethod($method) {
749
        switch ($method) {
750
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
751
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
752
            case 'http://www.w3.org/2001/10/xml-exc-c14n#':
753
            case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
754
                $this->canonicalMethod = $method;
755
                break;
756
            default:
757
                throw new Exception('Invalid Canonical Method');
758
        }
759
        if ($xpath = $this->getXPathObj()) {
760
            $query = './' . $this->searchpfx . ':SignedInfo';
761
            $nodeset = $xpath->query($query, $this->sigNode);
762
            if ($sinfo = $nodeset->item(0)) {
763
                $query = './' . $this->searchpfx . 'CanonicalizationMethod';
764
                $nodeset = $xpath->query($query, $sinfo);
765
                if (!($canonNode = $nodeset->item(0))) {
766
                    $canonNode = $this->createNewSignNode('CanonicalizationMethod');
767
                    $sinfo->insertBefore($canonNode, $sinfo->firstChild);
768
                }
769
                $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
770
            }
771
        }
772
    }
773
774
    private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
775
        $exclusive = FALSE;
776
        $withComments = FALSE;
777
        switch ($canonicalmethod) {
778
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
779
                $exclusive = FALSE;
780
                $withComments = FALSE;
781
                break;
782
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
783
                $withComments = TRUE;
784
                break;
785
            case 'http://www.w3.org/2001/10/xml-exc-c14n#':
786
                $exclusive = TRUE;
787
                break;
788
            case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
789
                $exclusive = TRUE;
790
                $withComments = TRUE;
791
                break;
792
        }
793
        /* Support PHP versions < 5.2 not containing C14N methods in DOM extension */
794
        $php_version = explode('.', PHP_VERSION);
795
        if (($php_version[0] < 5) || ($php_version[0] == 5 && $php_version[1] < 2)) {
796
            if (!is_null($arXPath)) {
797
                throw new Exception("PHP 5.2.0 or higher is required to perform XPath Transformations");
798
            }
799
            return C14NGeneral($node, $exclusive, $withComments);
800
        }
801
        return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
802
    }
803
804
    public function canonicalizeSignedInfo() {
805
806
        $doc = $this->sigNode->ownerDocument;
807
        $canonicalmethod = NULL;
808
        if ($doc) {
809
            $xpath = $this->getXPathObj();
810
            $query = "./secdsig:SignedInfo";
811
            $nodeset = $xpath->query($query, $this->sigNode);
812
            if ($signInfoNode = $nodeset->item(0)) {
813
                $query = "./secdsig:CanonicalizationMethod";
814
                $nodeset = $xpath->query($query, $signInfoNode);
815
                if ($canonNode = $nodeset->item(0)) {
816
                    $canonicalmethod = $canonNode->getAttribute('Algorithm');
817
                }
818
                $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
819
                return $this->signedInfo;
820
            }
821
        }
822
        return NULL;
823
    }
824
825
    public function calculateDigest($digestAlgorithm, $data) {
826
        switch ($digestAlgorithm) {
827
            case XMLSecurityDSig::SHA1:
828
                $alg = 'sha1';
829
                break;
830
            case XMLSecurityDSig::SHA256:
831
                $alg = 'sha256';
832
                break;
833
            case XMLSecurityDSig::SHA512:
834
                $alg = 'sha512';
835
                break;
836
            case XMLSecurityDSig::RIPEMD160:
837
                $alg = 'ripemd160';
838
                break;
839
            default:
840
                throw new Exception("Cannot validate digest: Unsupported Algorith <$digestAlgorithm>");
841
        }
842
        if (function_exists('hash')) {
843
            return base64_encode(hash($alg, $data, TRUE));
844
        } elseif (function_exists('mhash')) {
845
            $alg = "MHASH_" . strtoupper($alg);
846
            return base64_encode(mhash(constant($alg), $data));
847
        } elseif ($alg === 'sha1') {
848
            return base64_encode(sha1($data, TRUE));
849
        } else {
850
            throw new Exception('xmlseclibs is unable to calculate a digest. Maybe you need the mhash library?');
851
        }
852
    }
853
854
    public function validateDigest($refNode, $data) {
855
        $xpath = new DOMXPath($refNode->ownerDocument);
856
        $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
857
        $query = 'string(./secdsig:DigestMethod/@Algorithm)';
858
        $digestAlgorithm = $xpath->evaluate($query, $refNode);
859
        $digValue = $this->calculateDigest($digestAlgorithm, $data);
860
        $query = 'string(./secdsig:DigestValue)';
861
        $digestValue = $xpath->evaluate($query, $refNode);
862
        return ($digValue == $digestValue);
863
    }
864
865
    public function processTransforms($refNode, $objData, $includeCommentNodes = TRUE) {
866
        $data = $objData;
867
        $xpath = new DOMXPath($refNode->ownerDocument);
868
        $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
869
        $query = './secdsig:Transforms/secdsig:Transform';
870
        $nodelist = $xpath->query($query, $refNode);
871
        $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
872
        $arXPath = NULL;
873
        $prefixList = NULL;
874
        foreach ($nodelist AS $transform) {
875
            $algorithm = $transform->getAttribute("Algorithm");
876
            switch ($algorithm) {
877
                case 'http://www.w3.org/2001/10/xml-exc-c14n#':
878
                case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
879
880
                    if (!$includeCommentNodes) {
881
                        /* We remove comment nodes by forcing it to use a canonicalization
882
                         * without comments.
883
                         */
884
                        $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#';
885
                    } else {
886
                        $canonicalMethod = $algorithm;
887
                    }
888
889
                    $node = $transform->firstChild;
890
                    while ($node) {
891
                        if ($node->localName == 'InclusiveNamespaces') {
892
                            if ($pfx = $node->getAttribute('PrefixList')) {
893
                                $arpfx = array();
894
                                $pfxlist = explode(" ", $pfx);
895
                                foreach ($pfxlist AS $pfx) {
896
                                    $val = trim($pfx);
897
                                    if (!empty($val)) {
898
                                        $arpfx[] = $val;
899
                                    }
900
                                }
901
                                if (count($arpfx) > 0) {
902
                                    $prefixList = $arpfx;
903
                                }
904
                            }
905
                            break;
906
                        }
907
                        $node = $node->nextSibling;
908
                    }
909
                    break;
910
                case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
911
                case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
912
                    if (!$includeCommentNodes) {
913
                        /* We remove comment nodes by forcing it to use a canonicalization
914
                         * without comments.
915
                         */
916
                        $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
917
                    } else {
918
                        $canonicalMethod = $algorithm;
919
                    }
920
921
                    break;
922
                case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
923
                    $node = $transform->firstChild;
924
                    while ($node) {
925
                        if ($node->localName == 'XPath') {
926
                            $arXPath = array();
927
                            $arXPath['query'] = '(.//. | .//@* | .//namespace::*)[' . $node->nodeValue . ']';
928
                            $arXpath['namespaces'] = array();
929
                            $nslist = $xpath->query('./namespace::*', $node);
930
                            foreach ($nslist AS $nsnode) {
931
                                if ($nsnode->localName != "xml") {
932
                                    $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue;
933
                                }
934
                            }
935
                            break;
936
                        }
937
                        $node = $node->nextSibling;
938
                    }
939
                    break;
940
            }
941
        }
942
        if ($data instanceof DOMNode) {
943
            $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList);
944
        }
945
        return $data;
946
    }
947
948
    public function processRefNode($refNode) {
949
        $dataObject = NULL;
950
951
        /*
952
         * Depending on the URI, we may not want to include comments in the result
953
         * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel
954
         */
955
        $includeCommentNodes = TRUE;
956
957
        if ($uri = $refNode->getAttribute("URI")) {
958
            $arUrl = parse_url($uri);
959
            if (empty($arUrl['path'])) {
960
                if ($identifier = $arUrl['fragment']) {
961
962
                    /* This reference identifies a node with the given id by using
963
                     * a URI on the form "#identifier". This should not include comments.
964
                     */
965
                    $includeCommentNodes = FALSE;
966
967
                    $xPath = new DOMXPath($refNode->ownerDocument);
968
                    if ($this->idNS && is_array($this->idNS)) {
969
                        foreach ($this->idNS AS $nspf => $ns) {
970
                            $xPath->registerNamespace($nspf, $ns);
971
                        }
972
                    }
973
                    $iDlist = '@Id="' . $identifier . '"';
974
                    if (is_array($this->idKeys)) {
975
                        foreach ($this->idKeys AS $idKey) {
976
                            $iDlist .= " or @$idKey='$identifier'";
977
                        }
978
                    }
979
                    $query = '//*[' . $iDlist . ']';
980
                    $dataObject = $xPath->query($query)->item(0);
981
                } else {
982
                    $dataObject = $refNode->ownerDocument;
983
                }
984
            } else {
985
                $dataObject = file_get_contents($arUrl);
986
            }
987
        } else {
988
            /* This reference identifies the root node with an empty URI. This should
989
             * not include comments.
990
             */
991
            $includeCommentNodes = FALSE;
992
993
            $dataObject = $refNode->ownerDocument;
994
        }
995
        $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes);
996
        if (!$this->validateDigest($refNode, $data)) {
997
            return FALSE;
998
        }
999
1000
        if ($dataObject instanceof DOMNode) {
1001
            /* Add this node to the list of validated nodes. */
1002
            if (!empty($identifier)) {
1003
                $this->validatedNodes[$identifier] = $dataObject;
1004
            } else {
1005
                $this->validatedNodes[] = $dataObject;
1006
            }
1007
        }
1008
1009
        return TRUE;
1010
    }
1011
1012
    public function getRefNodeID($refNode) {
1013
        if ($uri = $refNode->getAttribute("URI")) {
1014
            $arUrl = parse_url($uri);
1015
            if (empty($arUrl['path'])) {
1016
                if ($identifier = $arUrl['fragment']) {
1017
                    return $identifier;
1018
                }
1019
            }
1020
        }
1021
        return null;
1022
    }
1023
1024
    public function getRefIDs() {
1025
        $refids = array();
1026
        $doc = $this->sigNode->ownerDocument;
1027
1028
        $xpath = $this->getXPathObj();
1029
        $query = "./secdsig:SignedInfo/secdsig:Reference";
1030
        $nodeset = $xpath->query($query, $this->sigNode);
1031
        if ($nodeset->length == 0) {
1032
            throw new Exception("Reference nodes not found");
1033
        }
1034
        foreach ($nodeset AS $refNode) {
1035
            $refids[] = $this->getRefNodeID($refNode);
1036
        }
1037
        return $refids;
1038
    }
1039
1040
    public function validateReference() {
1041
        $doc = $this->sigNode->ownerDocument;
1042
        if (!$doc->isSameNode($this->sigNode)) {
1043
            $this->sigNode->parentNode->removeChild($this->sigNode);
1044
        }
1045
        $xpath = $this->getXPathObj();
1046
        $query = "./secdsig:SignedInfo/secdsig:Reference";
1047
        $nodeset = $xpath->query($query, $this->sigNode);
1048
        if ($nodeset->length == 0) {
1049
            throw new Exception("Reference nodes not found");
1050
        }
1051
1052
        /* Initialize/reset the list of validated nodes. */
1053
        $this->validatedNodes = array();
1054
1055
        foreach ($nodeset AS $refNode) {
1056
            if (!$this->processRefNode($refNode)) {
1057
                /* Clear the list of validated nodes. */
1058
                $this->validatedNodes = NULL;
1059
                throw new Exception("Reference validation failed");
1060
            }
1061
        }
1062
        return TRUE;
1063
    }
1064
1065
    private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=NULL, $options=NULL) {
1066
        $prefix = NULL;
1067
        $prefix_ns = NULL;
1068
        $id_name = 'Id';
1069
        $overwrite_id = TRUE;
1070
        $force_uri = FALSE;
1071
1072
        if (is_array($options)) {
1073
            $prefix = empty($options['prefix']) ? NULL : $options['prefix'];
1074
            $prefix_ns = empty($options['prefix_ns']) ? NULL : $options['prefix_ns'];
1075
            $id_name = empty($options['id_name']) ? 'Id' : $options['id_name'];
1076
            $overwrite_id = !isset($options['overwrite']) ? TRUE : (bool) $options['overwrite'];
1077
            $force_uri = !isset($options['force_uri']) ? FALSE : (bool) $options['force_uri'];
1078
        }
1079
1080
        $attname = $id_name;
1081
        if (!empty($prefix)) {
1082
            $attname = $prefix . ':' . $attname;
1083
        }
1084
1085
        $refNode = $this->createNewSignNode('Reference');
1086
        $sinfoNode->appendChild($refNode);
1087
1088
        if (!$node instanceof DOMDocument) {
1089
            $uri = NULL;
1090
            if (!$overwrite_id) {
1091
                $uri = $node->getAttributeNS($prefix_ns, $attname);
1092
            }
1093
            if (empty($uri)) {
1094
                $uri = XMLSecurityDSig::generate_GUID();
1095
                $node->setAttributeNS($prefix_ns, $attname, $uri);
1096
            }
1097
            $refNode->setAttribute("URI", '#' . $uri);
1098
        } elseif ($force_uri) {
1099
            $refNode->setAttribute("URI", '');
1100
        }
1101
1102
        $transNodes = $this->createNewSignNode('Transforms');
1103
        $refNode->appendChild($transNodes);
1104
1105
        if (is_array($arTransforms)) {
1106
            foreach ($arTransforms AS $transform) {
1107
                $transNode = $this->createNewSignNode('Transform');
1108
                $transNodes->appendChild($transNode);
1109
                if (is_array($transform) &&
1110
                    (!empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
1111
                    (!empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
1112
                    $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
1113
                    $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
1114
                    $transNode->appendChild($XPathNode);
1115
                    if (!empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) {
1116
                        foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) {
1117
                            $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace);
1118
                        }
1119
                    }
1120
                } else {
1121
                    $transNode->setAttribute('Algorithm', $transform);
1122
                }
1123
            }
1124
        } elseif (!empty($this->canonicalMethod)) {
1125
            $transNode = $this->createNewSignNode('Transform');
1126
            $transNodes->appendChild($transNode);
1127
            $transNode->setAttribute('Algorithm', $this->canonicalMethod);
1128
        }
1129
1130
        $canonicalData = $this->processTransforms($refNode, $node);
1131
        $digValue = $this->calculateDigest($algorithm, $canonicalData);
1132
1133
        $digestMethod = $this->createNewSignNode('DigestMethod');
1134
        $refNode->appendChild($digestMethod);
1135
        $digestMethod->setAttribute('Algorithm', $algorithm);
1136
1137
        $digestValue = $this->createNewSignNode('DigestValue', $digValue);
1138
        $refNode->appendChild($digestValue);
1139
    }
1140
1141
    public function addReference($node, $algorithm, $arTransforms=NULL, $options=NULL) {
1142
        if ($xpath = $this->getXPathObj()) {
1143
            $query = "./secdsig:SignedInfo";
1144
            $nodeset = $xpath->query($query, $this->sigNode);
1145
            if ($sInfo = $nodeset->item(0)) {
1146
                $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
1147
            }
1148
        }
1149
    }
1150
1151
    public function addReferenceList($arNodes, $algorithm, $arTransforms=NULL, $options=NULL) {
1152
        if ($xpath = $this->getXPathObj()) {
1153
            $query = "./secdsig:SignedInfo";
1154
            $nodeset = $xpath->query($query, $this->sigNode);
1155
            if ($sInfo = $nodeset->item(0)) {
1156
                foreach ($arNodes AS $node) {
1157
                    $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
1158
                }
1159
            }
1160
        }
1161
    }
1162
1163
    public function addObject($data, $mimetype=NULL, $encoding=NULL) {
1164
        $objNode = $this->createNewSignNode('Object');
1165
        $this->sigNode->appendChild($objNode);
1166
        if (!empty($mimetype)) {
1167
            $objNode->setAtribute('MimeType', $mimetype);
1168
        }
1169
        if (!empty($encoding)) {
1170
            $objNode->setAttribute('Encoding', $encoding);
1171
        }
1172
1173
        if ($data instanceof DOMElement) {
1174
            $newData = $this->sigNode->ownerDocument->importNode($data, TRUE);
1175
        } else {
1176
            $newData = $this->sigNode->ownerDocument->createTextNode($data);
1177
        }
1178
        $objNode->appendChild($newData);
1179
1180
        return $objNode;
1181
    }
1182
1183
    public function locateKey($node=NULL) {
1184
        if (empty($node)) {
1185
            $node = $this->sigNode;
1186
        }
1187
        if (!$node instanceof DOMNode) {
1188
            return NULL;
1189
        }
1190
        if ($doc = $node->ownerDocument) {
1191
            $xpath = new DOMXPath($doc);
1192
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1193
            $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)";
1194
            $algorithm = $xpath->evaluate($query, $node);
1195
            if ($algorithm) {
1196
                try {
1197
                    $objKey = new XMLSecurityKey($algorithm, array('type' => 'public'));
1198
                } catch (Exception $e) {
1199
                    return NULL;
1200
                }
1201
                return $objKey;
1202
            }
1203
        }
1204
        return NULL;
1205
    }
1206
1207
    public function verify($objKey) {
1208
        $doc = $this->sigNode->ownerDocument;
1209
        $xpath = new DOMXPath($doc);
1210
        $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1211
        $query = "string(./secdsig:SignatureValue)";
1212
        $sigValue = $xpath->evaluate($query, $this->sigNode);
1213
        if (empty($sigValue)) {
1214
            throw new Exception("Unable to locate SignatureValue");
1215
        }
1216
        return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
1217
    }
1218
1219
    public function signData($objKey, $data) {
1220
        return $objKey->signData($data);
1221
    }
1222
1223
    public function sign($objKey, $appendToNode = NULL) {
1224
        // If we have a parent node append it now so C14N properly works
1225
        if ($appendToNode != NULL) {
1226
            $this->resetXPathObj();
1227
            $this->appendSignature($appendToNode);
1228
            $this->sigNode = $appendToNode->lastChild;
1229
        }
1230
        if ($xpath = $this->getXPathObj()) {
1231
            $query = "./secdsig:SignedInfo";
1232
            $nodeset = $xpath->query($query, $this->sigNode);
1233
            if ($sInfo = $nodeset->item(0)) {
1234
                $query = "./secdsig:SignatureMethod";
1235
                $nodeset = $xpath->query($query, $sInfo);
1236
                $sMethod = $nodeset->item(0);
1237
                $sMethod->setAttribute('Algorithm', $objKey->type);
1238
                $data = $this->canonicalizeData($sInfo, $this->canonicalMethod);
1239
                $sigValue = base64_encode($this->signData($objKey, $data));
1240
                $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
1241
                if ($infoSibling = $sInfo->nextSibling) {
1242
                    $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
1243
                } else {
1244
                    $this->sigNode->appendChild($sigValueNode);
1245
                }
1246
            }
1247
        }
1248
    }
1249
1250
    public function appendCert() {
1251
1252
    }
1253
1254
    public function appendKey($objKey, $parent=NULL) {
1255
        $objKey->serializeKey($parent);
1256
    }
1257
1258
    /**
1259
     * This function inserts the signature element.
1260
     *
1261
     * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode
1262
     * is specified, the signature element will be inserted as the last element before $beforeNode.
1263
     *
1264
     * @param $node  The node the signature element should be inserted into.
1265
     * @param $beforeNode  The node the signature element should be located before.
1266
     *
1267
     * @return DOMNode The signature element node
1268
     */
1269
    public function insertSignature($node, $beforeNode = NULL) {
1270
1271
        $document = $node->ownerDocument;
1272
        $signatureElement = $document->importNode($this->sigNode, TRUE);
1273
1274
        if ($beforeNode == NULL) {
1275
            return $node->insertBefore($signatureElement);
1276
        } else {
1277
            return $node->insertBefore($signatureElement, $beforeNode);
1278
        }
1279
    }
1280
1281
    public function appendSignature($parentNode, $insertBefore = FALSE) {
1282
        $beforeNode = $insertBefore ? $parentNode->firstChild : NULL;
1283
        return $this->insertSignature($parentNode, $beforeNode);
1284
    }
1285
1286
    static function get509XCert($cert, $isPEMFormat=TRUE) {
1287
        $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1288
        if (!empty($certs)) {
1289
            return $certs[0];
1290
        }
1291
        return '';
1292
    }
1293
1294
    static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
1295
        if ($isPEMFormat) {
1296
            $data = '';
1297
            $certlist = array();
1298
            $arCert = explode("\n", $certs);
1299
            $inData = FALSE;
1300
            foreach ($arCert AS $curData) {
1301
                if (!$inData) {
1302
                    if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
1303
                        $inData = TRUE;
1304
                    }
1305
                } else {
1306
                    if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
1307
                        $inData = FALSE;
1308
                        $certlist[] = $data;
1309
                        $data = '';
1310
                        continue;
1311
                    }
1312
                    $data .= trim($curData);
1313
                }
1314
            }
1315
            return $certlist;
1316
        } else {
1317
            return array($certs);
1318
        }
1319
    }
1320
1321
    static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL) {
1322
        if ($isURL) {
1323
            $cert = file_get_contents($cert);
1324
        }
1325
        if (!$parentRef instanceof DOMElement) {
1326
            throw new Exception('Invalid parent Node parameter');
1327
        }
1328
        $baseDoc = $parentRef->ownerDocument;
1329
1330
        if (empty($xpath)) {
1331
            $xpath = new DOMXPath($parentRef->ownerDocument);
1332
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1333
        }
1334
1335
        $query = "./secdsig:KeyInfo";
1336
        $nodeset = $xpath->query($query, $parentRef);
1337
        $keyInfo = $nodeset->item(0);
1338
        if (!$keyInfo) {
1339
            $inserted = FALSE;
1340
            $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo');
1341
1342
            $query = "./secdsig:Object";
1343
            $nodeset = $xpath->query($query, $parentRef);
1344
            if ($sObject = $nodeset->item(0)) {
1345
                $sObject->parentNode->insertBefore($keyInfo, $sObject);
1346
                $inserted = TRUE;
1347
            }
1348
1349
            if (!$inserted) {
1350
                $parentRef->appendChild($keyInfo);
1351
            }
1352
        }
1353
1354
        // Add all certs if there are more than one
1355
        $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1356
1357
        // Atach X509 data node
1358
        $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data');
1359
        $keyInfo->appendChild($x509DataNode);
1360
1361
        // Atach all certificate nodes
1362
        foreach ($certs as $X509Cert) {
1363
            $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
1364
            $x509DataNode->appendChild($x509CertNode);
1365
        }
1366
    }
1367
1368
    public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False) {
1369
        if ($xpath = $this->getXPathObj()) {
1370
            self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath);
1371
        }
1372
    }
1373
1374
    function addKeyInfoAndName($keyName, $xpath=NULL) {
1375
1376
        $baseDoc = $this->sigNode->ownerDocument;
1377
1378
        if (empty($xpath)) {
1379
            $xpath = new DOMXPath($baseDoc);
1380
            $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1381
        }
1382
1383
        $query = "./secdsig:KeyInfo";
1384
        $nodeset = $xpath->query($query, $this->sigNode);
1385
        $keyInfo = $nodeset->item(0);
1386
        if (!$keyInfo) {
1387
            $inserted = FALSE;
1388
            $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'KeyInfo');
1389
1390
            $query = "./secdsig:Object";
1391
            $nodeset = $xpath->query($query, $this->sigNode);
1392
            if ($sObject = $nodeset->item(0)) {
1393
                $sObject->parentNode->insertBefore($keyInfo, $sObject);
1394
                $inserted = TRUE;
1395
            }
1396
            if (!$inserted) {
1397
                $this->sigNode->appendChild($keyInfo);
1398
            }
1399
        }
1400
        $keyInfo->appendChild($baseDoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'KeyName', $keyName));
1401
    }
1402
1403
    /* This function retrieves an associative array of the validated nodes.
1404
     *
1405
     * The array will contain the id of the referenced node as the key and the node itself
1406
     * as the value.
1407
     *
1408
     * Returns:
1409
     *  An associative array of validated nodes or NULL if no nodes have been validated.
1410
     */
1411
1412
    public function getValidatedNodes() {
1413
        return $this->validatedNodes;
1414
    }
1415
1416
}
1417
1418
class XMLSecEnc {
1419
    const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
1420
   <xenc:CipherData>
1421
      <xenc:CipherValue></xenc:CipherValue>
1422
   </xenc:CipherData>
1423
</xenc:EncryptedData>";
1424
1425
    const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
1426
    const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
1427
    const URI = 3;
1428
    const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
1429
1430
    private $encdoc = NULL;
1431
    private $rawNode = NULL;
1432
    public $type = NULL;
1433
    public $encKey = NULL;
1434
    private $references = array();
1435
1436
    public function __construct() {
1437
        $this->_resetTemplate();
1438
    }
1439
1440
    private function _resetTemplate() {
1441
        $this->encdoc = new DOMDocument();
1442
        $this->encdoc->loadXML(XMLSecEnc::template);
1443
    }
1444
1445
    public function addReference($name, $node, $type) {
1446
        if (!$node instanceOf DOMNode) {
1447
            throw new Exception('$node is not of type DOMNode');
1448
        }
1449
        $curencdoc = $this->encdoc;
1450
        $this->_resetTemplate();
1451
        $encdoc = $this->encdoc;
1452
        $this->encdoc = $curencdoc;
1453
        $refuri = XMLSecurityDSig::generate_GUID();
1454
        $element = $encdoc->documentElement;
1455
        $element->setAttribute("Id", $refuri);
1456
        $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri);
1457
    }
1458
1459
    public function setNode($node) {
1460
        $this->rawNode = $node;
1461
    }
1462
1463
    public function encryptNode($objKey, $replace=TRUE) {
1464
        $data = '';
1465
        if (empty($this->rawNode)) {
1466
            throw new Exception('Node to encrypt has not been set');
1467
        }
1468
        if (!$objKey instanceof XMLSecurityKey) {
1469
            throw new Exception('Invalid Key');
1470
        }
1471
        $doc = $this->rawNode->ownerDocument;
1472
        $xPath = new DOMXPath($this->encdoc);
1473
        $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
1474
        $cipherValue = $objList->item(0);
1475
        if ($cipherValue == NULL) {
1476
            throw new Exception('Error locating CipherValue element within template');
1477
        }
1478
        switch ($this->type) {
1479
            case (XMLSecEnc::Element):
1480
                $data = $doc->saveXML($this->rawNode);
1481
                $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element);
1482
                break;
1483
            case (XMLSecEnc::Content):
1484
                $children = $this->rawNode->childNodes;
1485
                foreach ($children AS $child) {
1486
                    $data .= $doc->saveXML($child);
1487
                }
1488
                $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content);
1489
                break;
1490
            default:
1491
                throw new Exception('Type is currently not supported');
1492
                return;
1493
        }
1494
1495
        $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1496
        $encMethod->setAttribute('Algorithm', $objKey->getAlgorith());
1497
        $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild);
1498
1499
        $strEncrypt = base64_encode($objKey->encryptData($data));
1500
        $value = $this->encdoc->createTextNode($strEncrypt);
1501
        $cipherValue->appendChild($value);
1502
1503
        if ($replace) {
1504
            switch ($this->type) {
1505
                case (XMLSecEnc::Element):
1506
                    if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1507
                        return $this->encdoc;
1508
                    }
1509
                    $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1510
                    $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1511
                    return $importEnc;
1512
                    break;
1513
                case (XMLSecEnc::Content):
1514
                    $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1515
                    while ($this->rawNode->firstChild) {
1516
                        $this->rawNode->removeChild($this->rawNode->firstChild);
1517
                    }
1518
                    $this->rawNode->appendChild($importEnc);
1519
                    return $importEnc;
1520
                    break;
1521
            }
1522
        }
1523
    }
1524
1525
    public function encryptReferences($objKey) {
1526
        $curRawNode = $this->rawNode;
1527
        $curType = $this->type;
1528
        foreach ($this->references AS $name => $reference) {
1529
            $this->encdoc = $reference["encnode"];
1530
            $this->rawNode = $reference["node"];
1531
            $this->type = $reference["type"];
1532
            try {
1533
                $encNode = $this->encryptNode($objKey);
1534
                $this->references[$name]["encnode"] = $encNode;
1535
            } catch (Exception $e) {
1536
                $this->rawNode = $curRawNode;
1537
                $this->type = $curType;
1538
                throw $e;
1539
            }
1540
        }
1541
        $this->rawNode = $curRawNode;
1542
        $this->type = $curType;
1543
    }
1544
1545
    /**
1546
     * Retrieve the CipherValue text from this encrypted node.
1547
     *
1548
     * @return string|NULL  The Ciphervalue text, or NULL if no CipherValue is found.
1549
     */
1550
    public function getCipherValue() {
1551
        if (empty($this->rawNode)) {
1552
            throw new Exception('Node to decrypt has not been set');
1553
        }
1554
1555
        $doc = $this->rawNode->ownerDocument;
1556
        $xPath = new DOMXPath($doc);
1557
        $xPath->registerNamespace('xmlencr', XMLSecEnc::XMLENCNS);
1558
        /* Only handles embedded content right now and not a reference */
1559
        $query = "./xmlencr:CipherData/xmlencr:CipherValue";
1560
        $nodeset = $xPath->query($query, $this->rawNode);
1561
        $node = $nodeset->item(0);
1562
1563
        if (!$node) {
1564
            return NULL;
1565
        }
1566
1567
        return base64_decode($node->nodeValue);
1568
    }
1569
1570
    /**
1571
     * Decrypt this encrypted node.
1572
     *
1573
     * The behaviour of this function depends on the value of $replace.
1574
     * If $replace is FALSE, we will return the decrypted data as a string.
1575
     * If $replace is TRUE, we will insert the decrypted element(s) into the
1576
     * document, and return the decrypted element(s).
1577
     *
1578
     * @params XMLSecurityKey $objKey  The decryption key that should be used when decrypting the node.
1579
     * @params boolean $replace  Whether we should replace the encrypted node in the XML document with the decrypted data. The default is TRUE.
1580
     * @return string|DOMElement  The decrypted data.
1581
     */
1582
    public function decryptNode($objKey, $replace=TRUE) {
1583
        if (!$objKey instanceof XMLSecurityKey) {
1584
            throw new Exception('Invalid Key');
1585
        }
1586
1587
        $encryptedData = $this->getCipherValue();
1588
        if ($encryptedData) {
1589
            $decrypted = $objKey->decryptData($encryptedData);
1590
            if ($replace) {
1591
                switch ($this->type) {
1592
                    case (XMLSecEnc::Element):
1593
                        $newdoc = new DOMDocument();
1594
                        $newdoc->loadXML($decrypted);
1595
                        if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1596
                            return $newdoc;
1597
                        }
1598
                        $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE);
1599
                        $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1600
                        return $importEnc;
1601
                        break;
1602
                    case (XMLSecEnc::Content):
1603
                        if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1604
                            $doc = $this->rawNode;
1605
                        } else {
1606
                            $doc = $this->rawNode->ownerDocument;
1607
                        }
1608
                        $newFrag = $doc->createDocumentFragment();
1609
                        $newFrag->appendXML($decrypted);
1610
                        $parent = $this->rawNode->parentNode;
1611
                        $parent->replaceChild($newFrag, $this->rawNode);
1612
                        return $parent;
1613
                        break;
1614
                    default:
1615
                        return $decrypted;
1616
                }
1617
            } else {
1618
                return $decrypted;
1619
            }
1620
        } else {
1621
            throw new Exception("Cannot locate encrypted data");
1622
        }
1623
    }
1624
1625
    public function encryptKey($srcKey, $rawKey, $append=TRUE) {
1626
        if ((!$srcKey instanceof XMLSecurityKey) || (!$rawKey instanceof XMLSecurityKey)) {
1627
            throw new Exception('Invalid Key');
1628
        }
1629
        $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
1630
        $root = $this->encdoc->documentElement;
1631
        $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey');
1632
        if ($append) {
1633
            $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild);
1634
            $keyInfo->appendChild($encKey);
1635
        } else {
1636
            $this->encKey = $encKey;
1637
        }
1638
        $encMethod = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1639
        $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
1640
        if (!empty($srcKey->name)) {
1641
            $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1642
            $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
1643
        }
1644
        $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData'));
1645
        $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey));
1646
        if (is_array($this->references) && count($this->references) > 0) {
1647
            $refList = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:ReferenceList'));
1648
            foreach ($this->references AS $name => $reference) {
1649
                $refuri = $reference["refuri"];
1650
                $dataRef = $refList->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:DataReference'));
1651
                $dataRef->setAttribute("URI", '#' . $refuri);
1652
            }
1653
        }
1654
        return;
1655
    }
1656
1657
    public function decryptKey($encKey) {
1658
        if (!$encKey->isEncrypted) {
1659
            throw new Exception("Key is not Encrypted");
1660
        }
1661
        if (empty($encKey->key)) {
1662
            throw new Exception("Key is missing data to perform the decryption");
1663
        }
1664
        return $this->decryptNode($encKey, FALSE);
1665
    }
1666
1667
    public function locateEncryptedData($element) {
1668
        if ($element instanceof DOMDocument) {
1669
            $doc = $element;
1670
        } else {
1671
            $doc = $element->ownerDocument;
1672
        }
1673
        if ($doc) {
1674
            $xpath = new DOMXPath($doc);
1675
            $query = "//*[local-name()='EncryptedData' and namespace-uri()='" . XMLSecEnc::XMLENCNS . "']";
1676
            $nodeset = $xpath->query($query);
1677
            return $nodeset->item(0);
1678
        }
1679
        return NULL;
1680
    }
1681
1682
    public function locateKey($node=NULL) {
1683
        if (empty($node)) {
1684
            $node = $this->rawNode;
1685
        }
1686
        if (!$node instanceof DOMNode) {
1687
            return NULL;
1688
        }
1689
        if ($doc = $node->ownerDocument) {
1690
            $xpath = new DOMXPath($doc);
1691
            $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1692
            $query = ".//xmlsecenc:EncryptionMethod";
1693
            $nodeset = $xpath->query($query, $node);
1694
            if ($encmeth = $nodeset->item(0)) {
1695
                $attrAlgorithm = $encmeth->getAttribute("Algorithm");
1696
                try {
1697
                    $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private'));
1698
                } catch (Exception $e) {
1699
                    return NULL;
1700
                }
1701
                return $objKey;
1702
            }
1703
        }
1704
        return NULL;
1705
    }
1706
1707
    static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) {
1708
        if (empty($node) || (!$node instanceof DOMNode)) {
1709
            return NULL;
1710
        }
1711
        $doc = $node->ownerDocument;
1712
        if (!$doc) {
1713
            return NULL;
1714
        }
1715
1716
        $xpath = new DOMXPath($doc);
1717
        $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1718
        $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS);
1719
        $query = "./xmlsecdsig:KeyInfo";
1720
        $nodeset = $xpath->query($query, $node);
1721
        $encmeth = $nodeset->item(0);
1722
        if (!$encmeth) {
1723
            /* No KeyInfo in EncryptedData / EncryptedKey. */
1724
            return $objBaseKey;
1725
        }
1726
1727
        foreach ($encmeth->childNodes AS $child) {
1728
            switch ($child->localName) {
1729
                case 'KeyName':
1730
                    if (!empty($objBaseKey)) {
1731
                        $objBaseKey->name = $child->nodeValue;
1732
                    }
1733
                    break;
1734
                case 'KeyValue':
1735
                    foreach ($child->childNodes AS $keyval) {
1736
                        switch ($keyval->localName) {
1737
                            case 'DSAKeyValue':
1738
                                throw new Exception("DSAKeyValue currently not supported");
1739
                                break;
1740
                            case 'RSAKeyValue':
1741
                                $modulus = NULL;
1742
                                $exponent = NULL;
1743
                                if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
1744
                                    $modulus = base64_decode($modulusNode->nodeValue);
1745
                                }
1746
                                if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
1747
                                    $exponent = base64_decode($exponentNode->nodeValue);
1748
                                }
1749
                                if (empty($modulus) || empty($exponent)) {
1750
                                    throw new Exception("Missing Modulus or Exponent");
1751
                                }
1752
                                $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
1753
                                $objBaseKey->loadKey($publicKey);
1754
                                break;
1755
                        }
1756
                    }
1757
                    break;
1758
                case 'RetrievalMethod':
1759
                    $type = $child->getAttribute('Type');
1760
                    if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') {
1761
                        /* Unsupported key type. */
1762
                        break;
1763
                    }
1764
                    $uri = $child->getAttribute('URI');
1765
                    if ($uri[0] !== '#') {
1766
                        /* URI not a reference - unsupported. */
1767
                        break;
1768
                    }
1769
                    $id = substr($uri, 1);
1770
1771
                    $query = "//xmlsecenc:EncryptedKey[@Id='$id']";
1772
                    $keyElement = $xpath->query($query)->item(0);
1773
                    if (!$keyElement) {
1774
                        throw new Exception("Unable to locate EncryptedKey with @Id='$id'.");
1775
                    }
1776
1777
                    return XMLSecurityKey::fromEncryptedKeyElement($keyElement);
1778
                case 'EncryptedKey':
1779
                    return XMLSecurityKey::fromEncryptedKeyElement($child);
1780
                case 'X509Data':
1781
                    if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
1782
                        if ($x509certNodes->length > 0) {
1783
                            $x509cert = $x509certNodes->item(0)->textContent;
1784
                            $x509cert = str_replace(array("\r", "\n"), "", $x509cert);
1785
                            $x509cert = "-----BEGIN CERTIFICATE-----\n" . chunk_split($x509cert, 64, "\n") . "-----END CERTIFICATE-----\n";
1786
                            $objBaseKey->loadKey($x509cert, FALSE, TRUE);
1787
                        }
1788
                    }
1789
                    break;
1790
            }
1791
        }
1792
        return $objBaseKey;
1793
    }
1794
1795
    public function locateKeyInfo($objBaseKey=NULL, $node=NULL) {
1796
        if (empty($node)) {
1797
            $node = $this->rawNode;
1798
        }
1799
        return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node);
1800
    }
1801
1802
1803
}
1804