Test Setup Failed
Push — master ( f71949...6c6bd7 )
by Julito
55:21
created

XMLSecurityKey::fromEncryptedKeyElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 2
nop 1
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
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 View Code Duplication
            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 View Code Duplication
            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 View Code Duplication
            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 View Code Duplication
            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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
249 View Code Duplication
            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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
262 View Code Duplication
            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;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
274 View Code Duplication
            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;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
287 View Code Duplication
            case (self::RSA_SHA384):
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
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 View Code Duplication
            case (self::RSA_SHA512):
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->type (integer) and self::TRIPLEDES_CBC (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
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 View Code Duplication
    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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
478
            }
479
        }
480
        return $encrypted_data;
481
    }
482
483 View Code Duplication
    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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
488
            }
489
        } else {
490
            if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
491
                throw new Exception('Failure decrypting Data');
492
                return;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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 {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
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#">
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected TEMPLATE).
Loading history...
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 View Code Duplication
    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 View Code Duplication
    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 View Code Duplication
    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 {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1427
    const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected TEMPLATE).
Loading history...
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';
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected ELEMENT).
Loading history...
1434
    const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected CONTENT).
Loading history...
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;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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 View Code Duplication
                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 View Code Duplication
                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;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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);
0 ignored issues
show
Compatibility introduced by
$keyElement of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
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