|
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;
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
287
|
|
View Code Duplication |
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
|
|
View Code Duplication |
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
|
|
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;
|
|
|
|
|
|
|
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
|
|
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;
|
|
|
|
|
|
|
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
|
|
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 {
|
|
|
|
|
|
|
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
|
|
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;
|
|
|
|
|
|
|
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
|
|
|
|
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,dieorexitstatements that have been added for debug purposes.In the above example, the last
return falsewill never be executed, because a return statement has already been met in every possible execution path.