Test Setup Failed
Push — master ( 4e700f...c7183e )
by Julito
63:12
created

XMLSecEnc   C

Complexity

Total Complexity 77

Size/Duplication

Total Lines 395
Duplicated Lines 4.05 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 16
loc 395
rs 5.4715
c 0
b 0
f 0
wmc 77
lcom 1
cbo 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like XMLSecEnc often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XMLSecEnc, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * xmlseclibs.php
4
 *
5
 * Copyright (c) 2007-2013, Robert Richards <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 *
12
 *   * Redistributions of source code must retain the above copyright
13
 *     notice, this list of conditions and the following disclaimer.
14
 *
15
 *   * Redistributions in binary form must reproduce the above copyright
16
 *     notice, this list of conditions and the following disclaimer in
17
 *     the documentation and/or other materials provided with the
18
 *     distribution.
19
 *
20
 *   * Neither the name of Robert Richards nor the names of his
21
 *     contributors may be used to endorse or promote products derived
22
 *     from this software without specific prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35
 * POSSIBILITY OF SUCH DAMAGE.
36
 *
37
 * @author     Robert Richards <[email protected]>
38
 * @copyright  2007-2013 Robert Richards <[email protected]>
39
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
40
 * @version    1.3.1
41
 */
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
function canonical($tree, $element, $withcomments) {
62
    if ($tree->nodeType != XML_DOCUMENT_NODE) {
63
        $dom = $tree->ownerDocument;
64
    } else {
65
        $dom = $tree;
66
    }
67
    if ($element->nodeType != XML_ELEMENT_NODE) {
68
        if ($element->nodeType == XML_DOCUMENT_NODE) {
69
            foreach ($element->childNodes AS $node) {
70
                canonical($dom, $node, $withcomments);
71
            }
72
            return;
73
        }
74
        if ($element->nodeType == XML_COMMENT_NODE && ! $withcomments) {
75
            return;
76
        }
77
        $tree->appendChild($dom->importNode($element, TRUE));
78
        return;
79
    }
80
    $arNS = array();
81
    if ($element->namespaceURI != "") {
82
        if ($element->prefix == "") {
83
            $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
84
        } else {
85
            $prefix = $tree->lookupPrefix($element->namespaceURI);
86
            if ($prefix == $element->prefix) {
87
                $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
88
            } else {
89
                $elCopy = $dom->createElement($element->nodeName);
90
                $arNS[$element->namespaceURI] = $element->prefix;
91
            }
92
        }
93
    } else {
94
        $elCopy = $dom->createElement($element->nodeName);
95
    }
96
    $tree->appendChild($elCopy);
97
98
    /* Create DOMXPath based on original document */
99
    $xPath = new DOMXPath($element->ownerDocument);
100
101
    /* Get namespaced attributes */
102
    $arAtts = $xPath->query('attribute::*[namespace-uri(.) != ""]', $element);
103
104
    /* Create an array with namespace URIs as keys, and sort them */
105
    foreach ($arAtts AS $attnode) {
106
        if (array_key_exists($attnode->namespaceURI, $arNS) &&
107
            ($arNS[$attnode->namespaceURI] == $attnode->prefix)) {
108
            continue;
109
        }
110
        $prefix = $tree->lookupPrefix($attnode->namespaceURI);
111
        if ($prefix != $attnode->prefix) {
112
            $arNS[$attnode->namespaceURI] = $attnode->prefix;
113
        } else {
114
            $arNS[$attnode->namespaceURI] = NULL;
115
        }
116
    }
117
    if (count($arNS) > 0) {
118
        asort($arNS);
119
    }
120
121
    /* Add namespace nodes */
122
    foreach ($arNS AS $namespaceURI=>$prefix) {
123
        if ($prefix != NULL) {
124
            $elCopy->setAttributeNS("http://www.w3.org/2000/xmlns/",
125
                "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
function C14NGeneral($element, $exclusive=FALSE, $withcomments=FALSE) {
153
    /* IF PHP 5.2+ then use built in canonical functionality */
154
    $php_version = explode('.', PHP_VERSION);
155
    if (($php_version[0] > 5) || ($php_version[0] == 5 && $php_version[1] >= 2) ) {
156
        return $element->C14N($exclusive, $withcomments);
157
    }
158
159
    /* Must be element or document */
160
    if (! $element instanceof DOMElement && ! $element instanceof DOMDocument) {
161
        return NULL;
162
    }
163
    /* Currently only exclusive XML is supported */
164
    if ($exclusive == FALSE) {
165
        throw new Exception("Only exclusive canonicalization is supported in this version of PHP");
166
    }
167
168
    $copyDoc = new DOMDocument();
169
    canonical($copyDoc, $element, $withcomments);
170
    return $copyDoc->saveXML($copyDoc->documentElement, LIBXML_NOEMPTYTAG);
171
}
172
173
class XMLSecurityKey {
174
    const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
175
    const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
176
    const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
177
    const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
178
    const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
179
    const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
180
    const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
181
    const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
182
    const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
183
    const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
184
    const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
185
186
    private $cryptParams = array();
187
    public $type = 0;
188
    public $key = NULL;
189
    public $passphrase = "";
190
    public $iv = NULL;
191
    public $name = NULL;
192
    public $keyChain = NULL;
193
    public $isEncrypted = FALSE;
194
    public $encryptedCtx = NULL;
195
    public $guid = NULL;
196
197
    /**
198
     * This variable contains the certificate as a string if this key represents an X509-certificate.
199
     * If this key doesn't represent a certificate, this will be NULL.
200
     */
201
    private $x509Certificate = NULL;
202
203
    /* This variable contains the certificate thunbprint if we have loaded an X509-certificate. */
204
    private $X509Thumbprint = NULL;
205
206
    public function __construct($type, $params=NULL) {
207
        srand();
208
        switch ($type) {
209
            case (self::TRIPLEDES_CBC):
210
                $this->cryptParams['library'] = 'mcrypt';
211
                $this->cryptParams['cipher'] = MCRYPT_TRIPLEDES;
212
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
213
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
214
                $this->cryptParams['keysize'] = 24;
215
                break;
216
            case (self::AES128_CBC):
217
                $this->cryptParams['library'] = 'mcrypt';
218
                $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
219
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
220
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
221
                $this->cryptParams['keysize'] = 16;
222
                break;
223
            case (self::AES192_CBC):
224
                $this->cryptParams['library'] = 'mcrypt';
225
                $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
226
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
227
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
228
                $this->cryptParams['keysize'] = 24;
229
                break;
230
            case (self::AES256_CBC):
231
                $this->cryptParams['library'] = 'mcrypt';
232
                $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
233
                $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
234
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
235
                $this->cryptParams['keysize'] = 32;
236
                break;
237
            case (self::RSA_1_5):
238
                $this->cryptParams['library'] = 'openssl';
239
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
240
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
241
                if (is_array($params) && ! empty($params['type'])) {
242
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
243
                        $this->cryptParams['type'] = $params['type'];
244
                        break;
245
                    }
246
                }
247
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
248
                return;
249
            case (self::RSA_OAEP_MGF1P):
250
                $this->cryptParams['library'] = 'openssl';
251
                $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
252
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
253
                $this->cryptParams['hash'] = NULL;
254
                if (is_array($params) && ! empty($params['type'])) {
255
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
256
                        $this->cryptParams['type'] = $params['type'];
257
                        break;
258
                    }
259
                }
260
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
261
                return;
262
            case (self::RSA_SHA1):
263
                $this->cryptParams['library'] = 'openssl';
264
                $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
265
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
266
                if (is_array($params) && ! empty($params['type'])) {
267
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
268
                        $this->cryptParams['type'] = $params['type'];
269
                        break;
270
                    }
271
                }
272
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
273
                break;
274
            case (self::RSA_SHA256):
275
                $this->cryptParams['library'] = 'openssl';
276
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
277
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
278
                $this->cryptParams['digest'] = 'SHA256';
279
                if (is_array($params) && ! empty($params['type'])) {
280
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
281
                        $this->cryptParams['type'] = $params['type'];
282
                        break;
283
                    }
284
                }
285
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
286
                break;
287
            case (self::RSA_SHA384):
288
                $this->cryptParams['library'] = 'openssl';
289
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
290
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
291
                $this->cryptParams['digest'] = 'SHA384';
292
                if (is_array($params) && ! empty($params['type'])) {
293
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
294
                        $this->cryptParams['type'] = $params['type'];
295
                        break;
296
                    }
297
                }
298
            case (self::RSA_SHA512):
299
                $this->cryptParams['library'] = 'openssl';
300
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
301
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
302
                $this->cryptParams['digest'] = 'SHA512';
303
                if (is_array($params) && ! empty($params['type'])) {
304
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
305
                        $this->cryptParams['type'] = $params['type'];
306
                        break;
307
                    }
308
                }
309
            default:
310
                throw new Exception('Invalid Key Type');
311
                return;
312
        }
313
        $this->type = $type;
314
    }
315
316
    /**
317
     * Retrieve the key size for the symmetric encryption algorithm..
318
     *
319
     * If the key size is unknown, or this isn't a symmetric encryption algorithm,
320
     * NULL is returned.
321
     *
322
     * @return int|NULL  The number of bytes in the key.
323
     */
324
    public function getSymmetricKeySize() {
325
        if (! isset($this->cryptParams['keysize'])) {
326
            return NULL;
327
        }
328
        return $this->cryptParams['keysize'];
329
    }
330
331
    public function generateSessionKey() {
332
        if (!isset($this->cryptParams['keysize'])) {
333
            throw new Exception('Unknown key size for type "' . $this->type . '".');
334
        }
335
        $keysize = $this->cryptParams['keysize'];
336
337
        if (function_exists('openssl_random_pseudo_bytes')) {
338
            /* We have PHP >= 5.3 - use openssl to generate session key. */
339
            $key = openssl_random_pseudo_bytes($keysize);
340
        } else {
341
            /* Generating random key using iv generation routines */
342
            $key = mcrypt_create_iv($keysize, MCRYPT_RAND);
343
        }
344
345
        if ($this->type === self::TRIPLEDES_CBC) {
346
            /* Make sure that the generated key has the proper parity bits set.
347
             * Mcrypt doesn't care about the parity bits, but others may care.
348
            */
349
            for ($i = 0; $i < strlen($key); $i++) {
350
                $byte = ord($key[$i]) & 0xfe;
351
                $parity = 1;
352
                for ($j = 1; $j < 8; $j++) {
353
                    $parity ^= ($byte >> $j) & 1;
354
                }
355
                $byte |= $parity;
356
                $key[$i] = chr($byte);
357
            }
358
        }
359
360
        $this->key = $key;
361
        return $key;
362
    }
363
364
    public static function getRawThumbprint($cert) {
365
366
        $arCert = explode("\n", $cert);
367
        $data = '';
368
        $inData = FALSE;
369
370
        foreach ($arCert AS $curData) {
371
            if (! $inData) {
372
                if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
373
                    $inData = TRUE;
374
                }
375
            } else {
376
                if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
377
                    $inData = FALSE;
378
                    break;
379
                }
380
                $data .= trim($curData);
381
            }
382
        }
383
384
        if (! empty($data)) {
385
            return strtolower(sha1(base64_decode($data)));
386
        }
387
388
        return NULL;
389
    }
390
391
    public function loadKey($key, $isFile=FALSE, $isCert = FALSE) {
392
        if ($isFile) {
393
            $this->key = file_get_contents($key);
394
        } else {
395
            $this->key = $key;
396
        }
397
        if ($isCert) {
398
            $this->key = openssl_x509_read($this->key);
399
            openssl_x509_export($this->key, $str_cert);
400
            $this->x509Certificate = $str_cert;
401
            $this->key = $str_cert;
402
        } else {
403
            $this->x509Certificate = NULL;
404
        }
405
        if ($this->cryptParams['library'] == 'openssl') {
406
            if ($this->cryptParams['type'] == 'public') {
407
                if ($isCert) {
408
                    /* Load the thumbprint if this is an X509 certificate. */
409
                    $this->X509Thumbprint = self::getRawThumbprint($this->key);
410
                }
411
                $this->key = openssl_get_publickey($this->key);
412
413
            } else {
414
                $this->key = openssl_get_privatekey($this->key, $this->passphrase);
415
            }
416
        } else if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
417
            /* Check key length */
418
            switch ($this->type) {
419
                case (self::AES256_CBC):
420
                    if (strlen($this->key) < 25) {
421
                        throw new Exception('Key must contain at least 25 characters for this cipher');
422
                    }
423
                    break;
424
                case (self::AES192_CBC):
425
                    if (strlen($this->key) < 17) {
426
                        throw new Exception('Key must contain at least 17 characters for this cipher');
427
                    }
428
                    break;
429
            }
430
        }
431
    }
432
433
    private function encryptMcrypt($data) {
434
        $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
435
        $this->iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
436
        mcrypt_generic_init($td, $this->key, $this->iv);
437
        if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
438
            $bs = mcrypt_enc_get_block_size($td);
439
            for ($datalen0=$datalen=strlen($data); (($datalen%$bs)!=($bs-1)); $datalen++)
440
                $data.=chr(rand(1, 127));
441
            $data.=chr($datalen-$datalen0+1);
442
        }
443
        $encrypted_data = $this->iv.mcrypt_generic($td, $data);
444
        mcrypt_generic_deinit($td);
445
        mcrypt_module_close($td);
446
        return $encrypted_data;
447
    }
448
449
    private function decryptMcrypt($data) {
450
        $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
451
        $iv_length = mcrypt_enc_get_iv_size($td);
452
453
        $this->iv = substr($data, 0, $iv_length);
454
        $data = substr($data, $iv_length);
455
456
        mcrypt_generic_init($td, $this->key, $this->iv);
457
        $decrypted_data = mdecrypt_generic($td, $data);
458
        mcrypt_generic_deinit($td);
459
        mcrypt_module_close($td);
460
        if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
461
            $dataLen = strlen($decrypted_data);
462
            $paddingLength = substr($decrypted_data, $dataLen - 1, 1);
463
            $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
464
        }
465
        return $decrypted_data;
466
    }
467
468
    private function encryptOpenSSL($data) {
469
        if ($this->cryptParams['type'] == 'public') {
470
            if (! openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
471
                throw new Exception('Failure encrypting Data');
472
                return;
473
            }
474
        } else {
475
            if (! openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
476
                throw new Exception('Failure encrypting Data');
477
                return;
478
            }
479
        }
480
        return $encrypted_data;
481
    }
482
483
    private function decryptOpenSSL($data) {
484
        if ($this->cryptParams['type'] == 'public') {
485
            if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
486
                throw new Exception('Failure decrypting Data');
487
                return;
488
            }
489
        } else {
490
            if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
491
                throw new Exception('Failure decrypting Data');
492
                return;
493
            }
494
        }
495
        return $decrypted;
496
    }
497
498
    private function signOpenSSL($data) {
499
        $algo = OPENSSL_ALGO_SHA1;
500
        if (! empty($this->cryptParams['digest'])) {
501
            $algo = $this->cryptParams['digest'];
502
        }
503
504
        if (! openssl_sign ($data, $signature, $this->key, $algo)) {
505
            throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
506
            return;
507
        }
508
        return $signature;
509
    }
510
511
    private function verifyOpenSSL($data, $signature) {
512
        $algo = OPENSSL_ALGO_SHA1;
513
        if (! empty($this->cryptParams['digest'])) {
514
            $algo = $this->cryptParams['digest'];
515
        }
516
        return openssl_verify ($data, $signature, $this->key, $algo);
517
    }
518
519
    public function encryptData($data) {
520
        switch ($this->cryptParams['library']) {
521
            case 'mcrypt':
522
                return $this->encryptMcrypt($data);
523
                break;
524
            case 'openssl':
525
                return $this->encryptOpenSSL($data);
526
                break;
527
        }
528
    }
529
530
    public function decryptData($data) {
531
        switch ($this->cryptParams['library']) {
532
            case 'mcrypt':
533
                return $this->decryptMcrypt($data);
534
                break;
535
            case 'openssl':
536
                return $this->decryptOpenSSL($data);
537
                break;
538
        }
539
    }
540
541
    public function signData($data) {
542
        switch ($this->cryptParams['library']) {
543
            case 'openssl':
544
                return $this->signOpenSSL($data);
545
                break;
546
        }
547
    }
548
549
    public function verifySignature($data, $signature) {
550
        switch ($this->cryptParams['library']) {
551
            case 'openssl':
552
                return $this->verifyOpenSSL($data, $signature);
553
                break;
554
        }
555
    }
556
557
    public function getAlgorith() {
558
        return $this->cryptParams['method'];
559
    }
560
561
    static function makeAsnSegment($type, $string) {
562
        switch ($type){
563
            case 0x02:
564
                if (ord($string) > 0x7f)
565
                    $string = chr(0).$string;
566
                break;
567
            case 0x03:
568
                $string = chr(0).$string;
569
                break;
570
        }
571
572
        $length = strlen($string);
573
574
        if ($length < 128){
575
            $output = sprintf("%c%c%s", $type, $length, $string);
576
        } else if ($length < 0x0100){
577
            $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
578
        } else if ($length < 0x010000) {
579
            $output = sprintf("%c%c%c%c%s", $type, 0x82, $length/0x0100, $length%0x0100, $string);
580
        } else {
581
            $output = NULL;
582
        }
583
        return($output);
584
    }
585
586
    /* Modulus and Exponent must already be base64 decoded */
587
    static function convertRSA($modulus, $exponent) {
588
        /* make an ASN publicKeyInfo */
589
        $exponentEncoding = self::makeAsnSegment(0x02, $exponent);
590
        $modulusEncoding = self::makeAsnSegment(0x02, $modulus);
591
        $sequenceEncoding = self:: makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
592
        $bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding);
593
        $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
594
        $publicKeyInfo = self::makeAsnSegment (0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
595
596
        /* encode the publicKeyInfo in base64 and add PEM brackets */
597
        $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
598
        $encoding = "-----BEGIN PUBLIC KEY-----\n";
599
        $offset = 0;
600
        while ($segment=substr($publicKeyInfoBase64, $offset, 64)){
601
            $encoding = $encoding.$segment."\n";
602
            $offset += 64;
603
        }
604
        return $encoding."-----END PUBLIC KEY-----\n";
605
    }
606
607
    public function serializeKey($parent) {
608
609
    }
610
611
612
613
    /**
614
     * Retrieve the X509 certificate this key represents.
615
     *
616
     * Will return the X509 certificate in PEM-format if this key represents
617
     * an X509 certificate.
618
     *
619
     * @return  The X509 certificate or NULL if this key doesn't represent an X509-certificate.
620
     */
621
    public function getX509Certificate() {
622
        return $this->x509Certificate;
623
    }
624
625
    /* Get the thumbprint of this X509 certificate.
626
     *
627
     * Returns:
628
     *  The thumbprint as a lowercase 40-character hexadecimal number, or NULL
629
     *  if this isn't a X509 certificate.
630
     */
631
    public function getX509Thumbprint() {
632
        return $this->X509Thumbprint;
633
    }
634
635
636
    /**
637
     * Create key from an EncryptedKey-element.
638
     *
639
     * @param DOMElement $element  The EncryptedKey-element.
640
     * @return XMLSecurityKey  The new key.
641
     * @throws Exception
642
     */
643
    public static function fromEncryptedKeyElement(DOMElement $element) {
644
645
        $objenc = new XMLSecEnc();
646
        $objenc->setNode($element);
647
        if (! $objKey = $objenc->locateKey()) {
648
            throw new Exception("Unable to locate algorithm for this Encrypted Key");
649
        }
650
        $objKey->isEncrypted = TRUE;
651
        $objKey->encryptedCtx = $objenc;
652
        XMLSecEnc::staticLocateKeyInfo($objKey, $element);
653
        return $objKey;
654
    }
655
656
}
657
658
class XMLSecurityDSig {
659
    const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
660
    const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
661
    const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
662
    const SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384';
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 = '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
672
  <ds:SignedInfo>
673
    <ds:SignatureMethod />
674
  </ds:SignedInfo>
675
</ds: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 = 'ds';
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(self::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', self::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', self::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 (! is_null($value)) {
738
            $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.':'.$name, $value);
739
        } else {
740
            $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.':'.$name);
741
        }
742
        return $node;
743
    }
744
745
    public function setCanonicalMethod($method) {
746
        switch ($method) {
747
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
748
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
749
            case 'http://www.w3.org/2001/10/xml-exc-c14n#':
750
            case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
751
                $this->canonicalMethod = $method;
752
                break;
753
            default:
754
                throw new Exception('Invalid Canonical Method');
755
        }
756
        if ($xpath = $this->getXPathObj()) {
757
            $query = './'.$this->searchpfx.':SignedInfo';
758
            $nodeset = $xpath->query($query, $this->sigNode);
759
            if ($sinfo = $nodeset->item(0)) {
760
                $query = './'.$this->searchpfx.'CanonicalizationMethod';
761
                $nodeset = $xpath->query($query, $sinfo);
762
                if (! ($canonNode = $nodeset->item(0))) {
763
                    $canonNode = $this->createNewSignNode('CanonicalizationMethod');
764
                    $sinfo->insertBefore($canonNode, $sinfo->firstChild);
765
                }
766
                $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
767
            }
768
        }
769
    }
770
771
    private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
772
        $exclusive = FALSE;
773
        $withComments = FALSE;
774
        switch ($canonicalmethod) {
775
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
776
                $exclusive = FALSE;
777
                $withComments = FALSE;
778
                break;
779
            case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
780
                $withComments = TRUE;
781
                break;
782
            case 'http://www.w3.org/2001/10/xml-exc-c14n#':
783
                $exclusive = TRUE;
784
                break;
785
            case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
786
                $exclusive = TRUE;
787
                $withComments = TRUE;
788
                break;
789
        }
790
        /* Support PHP versions < 5.2 not containing C14N methods in DOM extension */
791
        $php_version = explode('.', PHP_VERSION);
792
        if (($php_version[0] < 5) || ($php_version[0] == 5 && $php_version[1] < 2) ) {
793
            if (! is_null($arXPath)) {
794
                throw new Exception("PHP 5.2.0 or higher is required to perform XPath Transformations");
795
            }
796
            return C14NGeneral($node, $exclusive, $withComments);
797
        }
798
        return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
799
    }
800
801
    public function canonicalizeSignedInfo() {
802
803
        $doc = $this->sigNode->ownerDocument;
804
        $canonicalmethod = NULL;
805
        if ($doc) {
806
            $xpath = $this->getXPathObj();
807
            $query = "./secdsig:SignedInfo";
808
            $nodeset = $xpath->query($query, $this->sigNode);
809
            if ($signInfoNode = $nodeset->item(0)) {
810
                $query = "./secdsig:CanonicalizationMethod";
811
                $nodeset = $xpath->query($query, $signInfoNode);
812
                if ($canonNode = $nodeset->item(0)) {
813
                    $canonicalmethod = $canonNode->getAttribute('Algorithm');
814
                }
815
                $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
816
                return $this->signedInfo;
817
            }
818
        }
819
        return NULL;
820
    }
821
822
    public function calculateDigest ($digestAlgorithm, $data) {
823
        switch ($digestAlgorithm) {
824
            case self::SHA1:
825
                $alg = 'sha1';
826
                break;
827
            case self::SHA256:
828
                $alg = 'sha256';
829
                break;
830
            case self::SHA384:
831
                $alg = 'sha384';
832
                break;
833
            case self::SHA512:
834
                $alg = 'sha512';
835
                break;
836
            case self::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', self::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', self::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
                    // no break
879
                case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
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, $id_name);
1092
            }
1093
            if (empty($uri)) {
1094
                $uri = self::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', self::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', self::XMLDSIGNS);
1211
        $query = "string(./secdsig:SignatureValue)";
1212
        $sigValue = $xpath->evaluate($query, $this->sigNode);
1213
1214
        if (empty($sigValue)) {
1215
            throw new Exception("Unable to locate SignatureValue");
1216
        }
1217
        error_log('-->>>>');
1218
        error_log(print_r($this->signedInfo,1));
1219
        error_log(print_r($sigValue,1));
1220
        error_log(print_r($objKey->verifySignature($this->signedInfo, base64_decode($sigValue)), 1));
1221
1222
        return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
1223
    }
1224
1225
    public function signData($objKey, $data) {
1226
        return $objKey->signData($data);
1227
    }
1228
1229
    public function sign($objKey, $appendToNode = NULL) {
1230
        // If we have a parent node append it now so C14N properly works
1231
        if ($appendToNode != NULL) {
1232
            $this->resetXPathObj();
1233
            $this->appendSignature($appendToNode);
1234
            $this->sigNode = $appendToNode->lastChild;
1235
        }
1236
        if ($xpath = $this->getXPathObj()) {
1237
            $query = "./secdsig:SignedInfo";
1238
            $nodeset = $xpath->query($query, $this->sigNode);
1239
            if ($sInfo = $nodeset->item(0)) {
1240
                $query = "./secdsig:SignatureMethod";
1241
                $nodeset = $xpath->query($query, $sInfo);
1242
                $sMethod = $nodeset->item(0);
1243
                $sMethod->setAttribute('Algorithm', $objKey->type);
1244
                $data = $this->canonicalizeData($sInfo, $this->canonicalMethod);
1245
                $sigValue = base64_encode($this->signData($objKey, $data));
1246
                $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
1247
                if ($infoSibling = $sInfo->nextSibling) {
1248
                    $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
1249
                } else {
1250
                    $this->sigNode->appendChild($sigValueNode);
1251
                }
1252
            }
1253
        }
1254
    }
1255
1256
    public function appendCert() {
1257
1258
    }
1259
1260
    public function appendKey($objKey, $parent=NULL) {
1261
        $objKey->serializeKey($parent);
1262
    }
1263
1264
1265
    /**
1266
     * This function inserts the signature element.
1267
     *
1268
     * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode
1269
     * is specified, the signature element will be inserted as the last element before $beforeNode.
1270
     *
1271
     * @param $node  The node the signature element should be inserted into.
1272
     * @param $beforeNode  The node the signature element should be located before.
1273
     *
1274
     * @return DOMNode The signature element node
1275
     */
1276
    public function insertSignature($node, $beforeNode = NULL) {
1277
1278
        $document = $node->ownerDocument;
1279
        $signatureElement = $document->importNode($this->sigNode, TRUE);
1280
1281
        if($beforeNode == NULL) {
1282
            return $node->insertBefore($signatureElement);
1283
        } else {
1284
            return $node->insertBefore($signatureElement, $beforeNode);
1285
        }
1286
    }
1287
1288
    public function appendSignature($parentNode, $insertBefore = FALSE) {
1289
        $beforeNode = $insertBefore ? $parentNode->firstChild : NULL;
1290
        return $this->insertSignature($parentNode, $beforeNode);
1291
    }
1292
1293
    static function get509XCert($cert, $isPEMFormat=TRUE) {
1294
        $certs = self::staticGet509XCerts($cert, $isPEMFormat);
1295
        if (! empty($certs)) {
1296
            return $certs[0];
1297
        }
1298
        return '';
1299
    }
1300
1301
    static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
1302
        if ($isPEMFormat) {
1303
            $data = '';
1304
            $certlist = array();
1305
            $arCert = explode("\n", $certs);
1306
            $inData = FALSE;
1307
            foreach ($arCert AS $curData) {
1308
                if (! $inData) {
1309
                    if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
1310
                        $inData = TRUE;
1311
                    }
1312
                } else {
1313
                    if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
1314
                        $inData = FALSE;
1315
                        $certlist[] = $data;
1316
                        $data = '';
1317
                        continue;
1318
                    }
1319
                    $data .= trim($curData);
1320
                }
1321
            }
1322
            return $certlist;
1323
        } else {
1324
            return array($certs);
1325
        }
1326
    }
1327
1328
    static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL, $options=NULL) {
1329
        if ($isURL) {
1330
            $cert = file_get_contents($cert);
1331
        }
1332
        if (! $parentRef instanceof DOMElement) {
1333
            throw new Exception('Invalid parent Node parameter');
1334
        }
1335
        $baseDoc = $parentRef->ownerDocument;
1336
1337
        if (empty($xpath)) {
1338
            $xpath = new DOMXPath($parentRef->ownerDocument);
1339
            $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
1340
        }
1341
1342
        $query = "./secdsig:KeyInfo";
1343
        $nodeset = $xpath->query($query, $parentRef);
1344
        $keyInfo = $nodeset->item(0);
1345
        if (! $keyInfo) {
1346
            $inserted = FALSE;
1347
            $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:KeyInfo');
1348
1349
            $query = "./secdsig:Object";
1350
            $nodeset = $xpath->query($query, $parentRef);
1351
            if ($sObject = $nodeset->item(0)) {
1352
                $sObject->parentNode->insertBefore($keyInfo, $sObject);
1353
                $inserted = TRUE;
1354
            }
1355
1356
            if (! $inserted) {
1357
                $parentRef->appendChild($keyInfo);
1358
            }
1359
        }
1360
1361
        // Add all certs if there are more than one
1362
        $certs = self::staticGet509XCerts($cert, $isPEMFormat);
1363
1364
        // Attach X509 data node
1365
        $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509Data');
1366
        $keyInfo->appendChild($x509DataNode);
1367
1368
        $issuerSerial = FALSE;
1369
        $subjectName = FALSE;
1370
        if (is_array($options)) {
1371
            if (! empty($options['issuerSerial'])) {
1372
                $issuerSerial = TRUE;
1373
            }
1374
        }
1375
1376
        // Attach all certificate nodes and any additional data
1377
        foreach ($certs as $X509Cert){
1378
            if ($issuerSerial) {
1379
                if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) {
1380
                    if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) {
1381
                        if (is_array($certData['issuer'])) {
1382
                            $parts = array();
1383
                            foreach ($certData['issuer'] AS $key => $value) {
1384
                                array_unshift($parts, "$key=$value");
1385
                            }
1386
                            $issuerName = implode(',', $parts);
1387
                        } else {
1388
                            $issuerName = $certData['issuer'];
1389
                        }
1390
1391
                        $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509IssuerSerial');
1392
                        $x509DataNode->appendChild($x509IssuerNode);
1393
1394
                        $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509IssuerName', $issuerName);
1395
                        $x509IssuerNode->appendChild($x509Node);
1396
                        $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509SerialNumber', $certData['serialNumber']);
1397
                        $x509IssuerNode->appendChild($x509Node);
1398
                    }
1399
                }
1400
1401
            }
1402
            $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
1403
            $x509DataNode->appendChild($x509CertNode);
1404
        }
1405
    }
1406
1407
    public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False, $options=NULL) {
1408
        if ($xpath = $this->getXPathObj()) {
1409
            self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options);
1410
        }
1411
    }
1412
1413
    /* This function retrieves an associative array of the validated nodes.
1414
     *
1415
     * The array will contain the id of the referenced node as the key and the node itself
1416
     * as the value.
1417
     *
1418
     * Returns:
1419
     *  An associative array of validated nodes or NULL if no nodes have been validated.
1420
     */
1421
    public function getValidatedNodes() {
1422
        return $this->validatedNodes;
1423
    }
1424
}
1425
1426
class XMLSecEnc {
1427
    const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
1428
   <xenc:CipherData>
1429
      <xenc:CipherValue></xenc:CipherValue>
1430
   </xenc:CipherData>
1431
</xenc:EncryptedData>";
1432
1433
    const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
1434
    const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
1435
    const URI = 3;
1436
    const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
1437
1438
    private $encdoc = NULL;
1439
    private $rawNode = NULL;
1440
    public $type = NULL;
1441
    public $encKey = NULL;
1442
    private $references = array();
1443
1444
    public function __construct() {
1445
        $this->_resetTemplate();
1446
    }
1447
1448
    private function _resetTemplate(){
1449
        $this->encdoc = new DOMDocument();
1450
        $this->encdoc->loadXML(self::template);
1451
    }
1452
1453
    public function addReference($name, $node, $type) {
1454
        if (! $node instanceOf DOMNode) {
1455
            throw new Exception('$node is not of type DOMNode');
1456
        }
1457
        $curencdoc = $this->encdoc;
1458
        $this->_resetTemplate();
1459
        $encdoc = $this->encdoc;
1460
        $this->encdoc = $curencdoc;
1461
        $refuri = XMLSecurityDSig::generate_GUID();
1462
        $element = $encdoc->documentElement;
1463
        $element->setAttribute("Id", $refuri);
1464
        $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri);
1465
    }
1466
1467
    public function setNode($node) {
1468
        $this->rawNode = $node;
1469
    }
1470
1471
    /**
1472
     * Encrypt the selected node with the given key.
1473
     *
1474
     * @param XMLSecurityKey $objKey  The encryption key and algorithm.
1475
     * @param bool $replace  Whether the encrypted node should be replaced in the original tree. Default is TRUE.
1476
     * @return DOMElement  The <xenc:EncryptedData>-element.
1477
     */
1478
    public function encryptNode($objKey, $replace=TRUE) {
1479
        $data = '';
1480
        if (empty($this->rawNode)) {
1481
            throw new Exception('Node to encrypt has not been set');
1482
        }
1483
        if (! $objKey instanceof XMLSecurityKey) {
1484
            throw new Exception('Invalid Key');
1485
        }
1486
        $doc = $this->rawNode->ownerDocument;
1487
        $xPath = new DOMXPath($this->encdoc);
1488
        $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
1489
        $cipherValue = $objList->item(0);
1490
        if ($cipherValue == NULL) {
1491
            throw new Exception('Error locating CipherValue element within template');
1492
        }
1493
        switch ($this->type) {
1494
            case (self::Element):
1495
                $data = $doc->saveXML($this->rawNode);
1496
                $this->encdoc->documentElement->setAttribute('Type', self::Element);
1497
                break;
1498
            case (self::Content):
1499
                $children = $this->rawNode->childNodes;
1500
                foreach ($children AS $child) {
1501
                    $data .= $doc->saveXML($child);
1502
                }
1503
                $this->encdoc->documentElement->setAttribute('Type', self::Content);
1504
                break;
1505
            default:
1506
                throw new Exception('Type is currently not supported');
1507
                return;
1508
        }
1509
1510
        $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
1511
        $encMethod->setAttribute('Algorithm', $objKey->getAlgorith());
1512
        $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild);
1513
1514
        $strEncrypt = base64_encode($objKey->encryptData($data));
1515
        $value = $this->encdoc->createTextNode($strEncrypt);
1516
        $cipherValue->appendChild($value);
1517
1518
        if ($replace) {
1519
            switch ($this->type) {
1520
                case (self::Element):
1521
                    if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1522
                        return $this->encdoc;
1523
                    }
1524
                    $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1525
                    $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1526
                    return $importEnc;
1527
                    break;
1528
                case (self::Content):
1529
                    $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1530
                    while($this->rawNode->firstChild) {
1531
                        $this->rawNode->removeChild($this->rawNode->firstChild);
1532
                    }
1533
                    $this->rawNode->appendChild($importEnc);
1534
                    return $importEnc;
1535
                    break;
1536
            }
1537
        } else {
1538
            return $this->encdoc->documentElement;
1539
        }
1540
    }
1541
1542
    public function encryptReferences($objKey) {
1543
        $curRawNode = $this->rawNode;
1544
        $curType = $this->type;
1545
        foreach ($this->references AS $name=>$reference) {
1546
            $this->encdoc = $reference["encnode"];
1547
            $this->rawNode = $reference["node"];
1548
            $this->type = $reference["type"];
1549
            try {
1550
                $encNode = $this->encryptNode($objKey);
1551
                $this->references[$name]["encnode"] = $encNode;
1552
            } catch (Exception $e) {
1553
                $this->rawNode = $curRawNode;
1554
                $this->type = $curType;
1555
                throw $e;
1556
            }
1557
        }
1558
        $this->rawNode = $curRawNode;
1559
        $this->type = $curType;
1560
    }
1561
1562
    /**
1563
     * Retrieve the CipherValue text from this encrypted node.
1564
     *
1565
     * @return string|NULL  The Ciphervalue text, or NULL if no CipherValue is found.
1566
     * @throws Exception
1567
     */
1568
    public function getCipherValue() {
1569
        if (empty($this->rawNode)) {
1570
            throw new Exception('Node to decrypt has not been set');
1571
        }
1572
1573
        $doc = $this->rawNode->ownerDocument;
1574
        $xPath = new DOMXPath($doc);
1575
        $xPath->registerNamespace('xmlencr', self::XMLENCNS);
1576
        /* Only handles embedded content right now and not a reference */
1577
        $query = "./xmlencr:CipherData/xmlencr:CipherValue";
1578
        $nodeset = $xPath->query($query, $this->rawNode);
1579
        $node = $nodeset->item(0);
1580
1581
        if (!$node) {
1582
            return NULL;
1583
        }
1584
1585
        return base64_decode($node->nodeValue);
1586
    }
1587
1588
    /**
1589
     * Decrypt this encrypted node.
1590
     *
1591
     * The behaviour of this function depends on the value of $replace.
1592
     * If $replace is FALSE, we will return the decrypted data as a string.
1593
     * If $replace is TRUE, we will insert the decrypted element(s) into the
1594
     * document, and return the decrypted element(s).
1595
     *
1596
     * @params XMLSecurityKey $objKey  The decryption key that should be used when decrypting the node.
1597
     * @params boolean $replace  Whether we should replace the encrypted node in the XML document with the decrypted data. The default is TRUE.
1598
     * @return string|DOMElement  The decrypted data.
1599
     * @throws Exception
1600
     */
1601
    public function decryptNode($objKey, $replace=TRUE) {
1602
        if (! $objKey instanceof XMLSecurityKey) {
1603
            throw new Exception('Invalid Key');
1604
        }
1605
1606
        $encryptedData = $this->getCipherValue();
1607
        if ($encryptedData) {
1608
            $decrypted = $objKey->decryptData($encryptedData);
1609
            if ($replace) {
1610
                switch ($this->type) {
1611
                    case (self::Element):
1612
                        $newdoc = new DOMDocument();
1613
                        $newdoc->loadXML($decrypted);
1614
                        if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1615
                            return $newdoc;
1616
                        }
1617
                        $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE);
1618
                        $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1619
                        return $importEnc;
1620
                        break;
1621
                    case (self::Content):
1622
                        if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1623
                            $doc = $this->rawNode;
1624
                        } else {
1625
                            $doc = $this->rawNode->ownerDocument;
1626
                        }
1627
                        $newFrag = $doc->createDocumentFragment();
1628
                        $newFrag->appendXML($decrypted);
1629
                        $parent = $this->rawNode->parentNode;
1630
                        $parent->replaceChild($newFrag, $this->rawNode);
1631
                        return $parent;
1632
                        break;
1633
                    default:
1634
                        return $decrypted;
1635
                }
1636
            } else {
1637
                return $decrypted;
1638
            }
1639
        } else {
1640
            throw new Exception("Cannot locate encrypted data");
1641
        }
1642
    }
1643
1644
    public function encryptKey($srcKey, $rawKey, $append=TRUE) {
1645
        if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) {
1646
            throw new Exception('Invalid Key');
1647
        }
1648
        $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
1649
        $root = $this->encdoc->documentElement;
1650
        $encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey');
1651
        if ($append) {
1652
            $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild);
1653
            $keyInfo->appendChild($encKey);
1654
        } else {
1655
            $this->encKey = $encKey;
1656
        }
1657
        $encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
1658
        $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
1659
        if (! empty($srcKey->name)) {
1660
            $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1661
            $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
1662
        }
1663
        $cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData'));
1664
        $cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey));
1665
        if (is_array($this->references) && count($this->references) > 0) {
1666
            $refList =  $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList'));
1667
            foreach ($this->references AS $name=>$reference) {
1668
                $refuri = $reference["refuri"];
1669
                $dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference'));
1670
                $dataRef->setAttribute("URI", '#' . $refuri);
1671
            }
1672
        }
1673
        return;
1674
    }
1675
1676
    public function decryptKey($encKey) {
1677
        if (! $encKey->isEncrypted) {
1678
            throw new Exception("Key is not Encrypted");
1679
        }
1680
        if (empty($encKey->key)) {
1681
            throw new Exception("Key is missing data to perform the decryption");
1682
        }
1683
        return $this->decryptNode($encKey, FALSE);
1684
    }
1685
1686
    public function locateEncryptedData($element) {
1687
        if ($element instanceof DOMDocument) {
1688
            $doc = $element;
1689
        } else {
1690
            $doc = $element->ownerDocument;
1691
        }
1692
        if ($doc) {
1693
            $xpath = new DOMXPath($doc);
1694
            $query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']";
1695
            $nodeset = $xpath->query($query);
1696
            return $nodeset->item(0);
1697
        }
1698
        return NULL;
1699
    }
1700
1701
    public function locateKey($node=NULL) {
1702
        if (empty($node)) {
1703
            $node = $this->rawNode;
1704
        }
1705
        if (! $node instanceof DOMNode) {
1706
            return NULL;
1707
        }
1708
        if ($doc = $node->ownerDocument) {
1709
            $xpath = new DOMXPath($doc);
1710
            $xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
1711
            $query = ".//xmlsecenc:EncryptionMethod";
1712
            $nodeset = $xpath->query($query, $node);
1713
            if ($encmeth = $nodeset->item(0)) {
1714
                $attrAlgorithm = $encmeth->getAttribute("Algorithm");
1715
                try {
1716
                    $objKey = new XMLSecurityKey($attrAlgorithm, array('type'=>'private'));
1717
                } catch (Exception $e) {
1718
                    return NULL;
1719
                }
1720
                return $objKey;
1721
            }
1722
        }
1723
        return NULL;
1724
    }
1725
1726
    static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) {
1727
        if (empty($node) || (! $node instanceof DOMNode)) {
1728
            return NULL;
1729
        }
1730
        $doc = $node->ownerDocument;
1731
        if (!$doc) {
1732
            return NULL;
1733
        }
1734
1735
        $xpath = new DOMXPath($doc);
1736
        $xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
1737
        $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS);
1738
        $query = "./xmlsecdsig:KeyInfo";
1739
        $nodeset = $xpath->query($query, $node);
1740
        $encmeth = $nodeset->item(0);
1741
        if (!$encmeth) {
1742
            /* No KeyInfo in EncryptedData / EncryptedKey. */
1743
            return $objBaseKey;
1744
        }
1745
1746
        foreach ($encmeth->childNodes AS $child) {
1747
            switch ($child->localName) {
1748
                case 'KeyName':
1749
                    if (! empty($objBaseKey)) {
1750
                        $objBaseKey->name = $child->nodeValue;
1751
                    }
1752
                    break;
1753
                case 'KeyValue':
1754
                    foreach ($child->childNodes AS $keyval) {
1755
                        switch ($keyval->localName) {
1756
                            case 'DSAKeyValue':
1757
                                throw new Exception("DSAKeyValue currently not supported");
1758
                                break;
1759
                            case 'RSAKeyValue':
1760
                                $modulus = NULL;
1761
                                $exponent = NULL;
1762
                                if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
1763
                                    $modulus = base64_decode($modulusNode->nodeValue);
1764
                                }
1765
                                if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
1766
                                    $exponent = base64_decode($exponentNode->nodeValue);
1767
                                }
1768
                                if (empty($modulus) || empty($exponent)) {
1769
                                    throw new Exception("Missing Modulus or Exponent");
1770
                                }
1771
                                $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
1772
                                $objBaseKey->loadKey($publicKey);
1773
                                break;
1774
                        }
1775
                    }
1776
                    break;
1777
                case 'RetrievalMethod':
1778
                    $type = $child->getAttribute('Type');
1779
                    if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') {
1780
                        /* Unsupported key type. */
1781
                        break;
1782
                    }
1783
                    $uri = $child->getAttribute('URI');
1784
                    if ($uri[0] !== '#') {
1785
                        /* URI not a reference - unsupported. */
1786
                        break;
1787
                    }
1788
                    $id = substr($uri, 1);
1789
1790
                    $query = "//xmlsecenc:EncryptedKey[@Id='$id']";
1791
                    $keyElement = $xpath->query($query)->item(0);
1792
                    if (!$keyElement) {
1793
                        throw new Exception("Unable to locate EncryptedKey with @Id='$id'.");
1794
                    }
1795
1796
                    return XMLSecurityKey::fromEncryptedKeyElement($keyElement);
1797
                case 'EncryptedKey':
1798
                    return XMLSecurityKey::fromEncryptedKeyElement($child);
1799
                case 'X509Data':
1800
                    if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
1801
                        if ($x509certNodes->length > 0) {
1802
                            $x509cert = $x509certNodes->item(0)->textContent;
1803
                            $x509cert = str_replace(array("\r", "\n"), "", $x509cert);
1804
                            $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
1805
                            $objBaseKey->loadKey($x509cert, FALSE, TRUE);
1806
                        }
1807
                    }
1808
                    break;
1809
            }
1810
        }
1811
        return $objBaseKey;
1812
    }
1813
1814
    public function locateKeyInfo($objBaseKey=NULL, $node=NULL) {
1815
        if (empty($node)) {
1816
            $node = $this->rawNode;
1817
        }
1818
        return self::staticLocateKeyInfo($objBaseKey, $node);
1819
    }
1820
}
1821