Rev 868 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 868 | Rev 1050 | ||
---|---|---|---|
1 | <?php |
1 | <?php |
2 | namespace RobRichards\XMLSecLibs; |
2 | namespace RobRichards\XMLSecLibs; |
3 | 3 | ||
4 | use DOMDocument; |
4 | use DOMDocument; |
5 | use DOMElement; |
5 | use DOMElement; |
6 | use DOMNode; |
6 | use DOMNode; |
7 | use DOMXPath; |
7 | use DOMXPath; |
8 | use Exception; |
8 | use Exception; |
9 | use RobRichards\XMLSecLibs\Utils\XPath as XPath; |
9 | use RobRichards\XMLSecLibs\Utils\XPath as XPath; |
10 | 10 | ||
11 | /** |
11 | /** |
12 | * xmlseclibs.php |
12 | * xmlseclibs.php |
13 | * |
13 | * |
14 | * Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>. |
14 | * Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>. |
15 | * All rights reserved. |
15 | * All rights reserved. |
16 | * |
16 | * |
17 | * Redistribution and use in source and binary forms, with or without |
17 | * Redistribution and use in source and binary forms, with or without |
18 | * modification, are permitted provided that the following conditions |
18 | * modification, are permitted provided that the following conditions |
19 | * are met: |
19 | * are met: |
20 | * |
20 | * |
21 | * * Redistributions of source code must retain the above copyright |
21 | * * Redistributions of source code must retain the above copyright |
22 | * notice, this list of conditions and the following disclaimer. |
22 | * notice, this list of conditions and the following disclaimer. |
23 | * |
23 | * |
24 | * * Redistributions in binary form must reproduce the above copyright |
24 | * * Redistributions in binary form must reproduce the above copyright |
25 | * notice, this list of conditions and the following disclaimer in |
25 | * notice, this list of conditions and the following disclaimer in |
26 | * the documentation and/or other materials provided with the |
26 | * the documentation and/or other materials provided with the |
27 | * distribution. |
27 | * distribution. |
28 | * |
28 | * |
29 | * * Neither the name of Robert Richards nor the names of his |
29 | * * Neither the name of Robert Richards nor the names of his |
30 | * contributors may be used to endorse or promote products derived |
30 | * contributors may be used to endorse or promote products derived |
31 | * from this software without specific prior written permission. |
31 | * from this software without specific prior written permission. |
32 | * |
32 | * |
33 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
33 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
34 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
34 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
35 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
35 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
36 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
36 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
37 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
37 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
38 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
38 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
39 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
39 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
40 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
40 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
41 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
41 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
42 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
42 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
43 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
43 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
44 | * POSSIBILITY OF SUCH DAMAGE. |
44 | * POSSIBILITY OF SUCH DAMAGE. |
45 | * |
45 | * |
46 | * @author Robert Richards <rrichards@cdatazone.org> |
46 | * @author Robert Richards <rrichards@cdatazone.org> |
47 | * @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org> |
47 | * @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org> |
48 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License |
48 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License |
49 | */ |
49 | */ |
50 | 50 | ||
51 | class XMLSecEnc |
51 | class XMLSecEnc |
52 | { |
52 | { |
53 | const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'> |
53 | const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'> |
54 | <xenc:CipherData> |
54 | <xenc:CipherData> |
55 | <xenc:CipherValue></xenc:CipherValue> |
55 | <xenc:CipherValue></xenc:CipherValue> |
56 | </xenc:CipherData> |
56 | </xenc:CipherData> |
57 | </xenc:EncryptedData>"; |
57 | </xenc:EncryptedData>"; |
58 | 58 | ||
59 | const Element = 'http://www.w3.org/2001/04/xmlenc#Element'; |
59 | const Element = 'http://www.w3.org/2001/04/xmlenc#Element'; |
60 | const Content = 'http://www.w3.org/2001/04/xmlenc#Content'; |
60 | const Content = 'http://www.w3.org/2001/04/xmlenc#Content'; |
61 | const URI = 3; |
61 | const URI = 3; |
62 | const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; |
62 | const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; |
63 | 63 | ||
64 | /** @var null|DOMDocument */ |
64 | /** @var null|DOMDocument */ |
65 | private $encdoc = null; |
65 | private $encdoc = null; |
66 | 66 | ||
67 | /** @var null|DOMNode */ |
67 | /** @var null|DOMNode */ |
68 | private $rawNode = null; |
68 | private $rawNode = null; |
69 | 69 | ||
70 | /** @var null|string */ |
70 | /** @var null|string */ |
71 | public $type = null; |
71 | public $type = null; |
72 | 72 | ||
73 | /** @var null|DOMElement */ |
73 | /** @var null|DOMElement */ |
74 | public $encKey = null; |
74 | public $encKey = null; |
75 | 75 | ||
76 | /** @var array */ |
76 | /** @var array */ |
77 | private $references = array(); |
77 | private $references = array(); |
78 | 78 | ||
79 | public function __construct() |
79 | public function __construct() |
80 | { |
80 | { |
81 | $this->_resetTemplate(); |
81 | $this->_resetTemplate(); |
82 | } |
82 | } |
83 | 83 | ||
84 | private function _resetTemplate() |
84 | private function _resetTemplate() |
85 | { |
85 | { |
86 | $this->encdoc = new DOMDocument(); |
86 | $this->encdoc = new DOMDocument(); |
87 | $this->encdoc->loadXML(self::template); |
87 | $this->encdoc->loadXML(self::template); |
88 | } |
88 | } |
89 | 89 | ||
90 | /** |
90 | /** |
91 | * @param string $name |
91 | * @param string $name |
92 | * @param DOMNode $node |
92 | * @param DOMNode $node |
93 | * @param string $type |
93 | * @param string $type |
94 | * @throws Exception |
94 | * @throws Exception |
95 | */ |
95 | */ |
96 | public function addReference($name, $node, $type) |
96 | public function addReference($name, $node, $type) |
97 | { |
97 | { |
98 | if (! $node instanceOf DOMNode) { |
98 | if (! $node instanceOf DOMNode) { |
99 | throw new Exception('$node is not of type DOMNode'); |
99 | throw new Exception('$node is not of type DOMNode'); |
100 | } |
100 | } |
101 | $curencdoc = $this->encdoc; |
101 | $curencdoc = $this->encdoc; |
102 | $this->_resetTemplate(); |
102 | $this->_resetTemplate(); |
103 | $encdoc = $this->encdoc; |
103 | $encdoc = $this->encdoc; |
104 | $this->encdoc = $curencdoc; |
104 | $this->encdoc = $curencdoc; |
105 | $refuri = XMLSecurityDSig::generateGUID(); |
105 | $refuri = XMLSecurityDSig::generateGUID(); |
106 | $element = $encdoc->documentElement; |
106 | $element = $encdoc->documentElement; |
107 | $element->setAttribute("Id", $refuri); |
107 | $element->setAttribute("Id", $refuri); |
108 | $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri); |
108 | $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri); |
109 | } |
109 | } |
110 | 110 | ||
111 | /** |
111 | /** |
112 | * @param DOMNode $node |
112 | * @param DOMNode $node |
113 | */ |
113 | */ |
114 | public function setNode($node) |
114 | public function setNode($node) |
115 | { |
115 | { |
116 | $this->rawNode = $node; |
116 | $this->rawNode = $node; |
117 | } |
117 | } |
118 | 118 | ||
119 | /** |
119 | /** |
120 | * Encrypt the selected node with the given key. |
120 | * Encrypt the selected node with the given key. |
121 | * |
121 | * |
122 | * @param XMLSecurityKey $objKey The encryption key and algorithm. |
122 | * @param XMLSecurityKey $objKey The encryption key and algorithm. |
123 | * @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true. |
123 | * @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true. |
124 | * @throws Exception |
124 | * @throws Exception |
125 | * |
125 | * |
126 | * @return DOMElement The <xenc:EncryptedData>-element. |
126 | * @return DOMElement The <xenc:EncryptedData>-element. |
127 | */ |
127 | */ |
128 | public function encryptNode($objKey, $replace = true) |
128 | public function encryptNode($objKey, $replace = true) |
129 | { |
129 | { |
130 | $data = ''; |
130 | $data = ''; |
131 | if (empty($this->rawNode)) { |
131 | if (empty($this->rawNode)) { |
132 | throw new Exception('Node to encrypt has not been set'); |
132 | throw new Exception('Node to encrypt has not been set'); |
133 | } |
133 | } |
134 | if (! $objKey instanceof XMLSecurityKey) { |
134 | if (! $objKey instanceof XMLSecurityKey) { |
135 | throw new Exception('Invalid Key'); |
135 | throw new Exception('Invalid Key'); |
136 | } |
136 | } |
137 | $doc = $this->rawNode->ownerDocument; |
137 | $doc = $this->rawNode->ownerDocument; |
138 | $xPath = new DOMXPath($this->encdoc); |
138 | $xPath = new DOMXPath($this->encdoc); |
139 | $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue'); |
139 | $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue'); |
140 | $cipherValue = $objList->item(0); |
140 | $cipherValue = $objList->item(0); |
141 | if ($cipherValue == null) { |
141 | if ($cipherValue == null) { |
142 | throw new Exception('Error locating CipherValue element within template'); |
142 | throw new Exception('Error locating CipherValue element within template'); |
143 | } |
143 | } |
144 | switch ($this->type) { |
144 | switch ($this->type) { |
145 | case (self::Element): |
145 | case (self::Element): |
146 | $data = $doc->saveXML($this->rawNode); |
146 | $data = $doc->saveXML($this->rawNode); |
147 | $this->encdoc->documentElement->setAttribute('Type', self::Element); |
147 | $this->encdoc->documentElement->setAttribute('Type', self::Element); |
148 | break; |
148 | break; |
149 | case (self::Content): |
149 | case (self::Content): |
150 | $children = $this->rawNode->childNodes; |
150 | $children = $this->rawNode->childNodes; |
151 | foreach ($children AS $child) { |
151 | foreach ($children AS $child) { |
152 | $data .= $doc->saveXML($child); |
152 | $data .= $doc->saveXML($child); |
153 | } |
153 | } |
154 | $this->encdoc->documentElement->setAttribute('Type', self::Content); |
154 | $this->encdoc->documentElement->setAttribute('Type', self::Content); |
155 | break; |
155 | break; |
156 | default: |
156 | default: |
157 | throw new Exception('Type is currently not supported'); |
157 | throw new Exception('Type is currently not supported'); |
158 | } |
158 | } |
159 | 159 | ||
160 | $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); |
160 | $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); |
161 | $encMethod->setAttribute('Algorithm', $objKey->getAlgorithm()); |
161 | $encMethod->setAttribute('Algorithm', $objKey->getAlgorithm()); |
162 | $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild); |
162 | $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild); |
163 | 163 | ||
164 | $strEncrypt = base64_encode($objKey->encryptData($data)); |
164 | $strEncrypt = base64_encode($objKey->encryptData($data)); |
165 | $value = $this->encdoc->createTextNode($strEncrypt); |
165 | $value = $this->encdoc->createTextNode($strEncrypt); |
166 | $cipherValue->appendChild($value); |
166 | $cipherValue->appendChild($value); |
167 | 167 | ||
168 | if ($replace) { |
168 | if ($replace) { |
169 | switch ($this->type) { |
169 | switch ($this->type) { |
170 | case (self::Element): |
170 | case (self::Element): |
171 | if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { |
171 | if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { |
172 | return $this->encdoc; |
172 | return $this->encdoc; |
173 | } |
173 | } |
174 | $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); |
174 | $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); |
175 | $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); |
175 | $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); |
176 | return $importEnc; |
176 | return $importEnc; |
177 | case (self::Content): |
177 | case (self::Content): |
178 | $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); |
178 | $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); |
179 | while ($this->rawNode->firstChild) { |
179 | while ($this->rawNode->firstChild) { |
180 | $this->rawNode->removeChild($this->rawNode->firstChild); |
180 | $this->rawNode->removeChild($this->rawNode->firstChild); |
181 | } |
181 | } |
182 | $this->rawNode->appendChild($importEnc); |
182 | $this->rawNode->appendChild($importEnc); |
183 | return $importEnc; |
183 | return $importEnc; |
184 | } |
184 | } |
185 | } else { |
185 | } else { |
186 | return $this->encdoc->documentElement; |
186 | return $this->encdoc->documentElement; |
187 | } |
187 | } |
188 | } |
188 | } |
189 | 189 | ||
190 | /** |
190 | /** |
191 | * @param XMLSecurityKey $objKey |
191 | * @param XMLSecurityKey $objKey |
192 | * @throws Exception |
192 | * @throws Exception |
193 | */ |
193 | */ |
194 | public function encryptReferences($objKey) |
194 | public function encryptReferences($objKey) |
195 | { |
195 | { |
196 | $curRawNode = $this->rawNode; |
196 | $curRawNode = $this->rawNode; |
197 | $curType = $this->type; |
197 | $curType = $this->type; |
198 | foreach ($this->references AS $name => $reference) { |
198 | foreach ($this->references AS $name => $reference) { |
199 | $this->encdoc = $reference["encnode"]; |
199 | $this->encdoc = $reference["encnode"]; |
200 | $this->rawNode = $reference["node"]; |
200 | $this->rawNode = $reference["node"]; |
201 | $this->type = $reference["type"]; |
201 | $this->type = $reference["type"]; |
202 | try { |
202 | try { |
203 | $encNode = $this->encryptNode($objKey); |
203 | $encNode = $this->encryptNode($objKey); |
204 | $this->references[$name]["encnode"] = $encNode; |
204 | $this->references[$name]["encnode"] = $encNode; |
205 | } catch (Exception $e) { |
205 | } catch (\Exception $e) { |
206 | $this->rawNode = $curRawNode; |
206 | $this->rawNode = $curRawNode; |
207 | $this->type = $curType; |
207 | $this->type = $curType; |
208 | throw $e; |
208 | throw $e; |
209 | } |
209 | } |
210 | } |
210 | } |
211 | $this->rawNode = $curRawNode; |
211 | $this->rawNode = $curRawNode; |
212 | $this->type = $curType; |
212 | $this->type = $curType; |
213 | } |
213 | } |
214 | 214 | ||
215 | /** |
215 | /** |
216 | * Retrieve the CipherValue text from this encrypted node. |
216 | * Retrieve the CipherValue text from this encrypted node. |
217 | * |
217 | * |
218 | * @throws Exception |
218 | * @throws Exception |
219 | * @return string|null The Ciphervalue text, or null if no CipherValue is found. |
219 | * @return string|null The Ciphervalue text, or null if no CipherValue is found. |
220 | */ |
220 | */ |
221 | public function getCipherValue() |
221 | public function getCipherValue() |
222 | { |
222 | { |
223 | if (empty($this->rawNode)) { |
223 | if (empty($this->rawNode)) { |
224 | throw new Exception('Node to decrypt has not been set'); |
224 | throw new Exception('Node to decrypt has not been set'); |
225 | } |
225 | } |
226 | 226 | ||
227 | $doc = $this->rawNode->ownerDocument; |
227 | $doc = $this->rawNode->ownerDocument; |
228 | $xPath = new DOMXPath($doc); |
228 | $xPath = new DOMXPath($doc); |
229 | $xPath->registerNamespace('xmlencr', self::XMLENCNS); |
229 | $xPath->registerNamespace('xmlencr', self::XMLENCNS); |
230 | /* Only handles embedded content right now and not a reference */ |
230 | /* Only handles embedded content right now and not a reference */ |
231 | $query = "./xmlencr:CipherData/xmlencr:CipherValue"; |
231 | $query = "./xmlencr:CipherData/xmlencr:CipherValue"; |
232 | $nodeset = $xPath->query($query, $this->rawNode); |
232 | $nodeset = $xPath->query($query, $this->rawNode); |
233 | $node = $nodeset->item(0); |
233 | $node = $nodeset->item(0); |
234 | 234 | ||
235 | if (!$node) { |
235 | if (!$node) { |
236 | return null; |
236 | return null; |
237 | } |
237 | } |
238 | 238 | ||
239 | return base64_decode($node->nodeValue); |
239 | return base64_decode($node->nodeValue); |
240 | } |
240 | } |
241 | 241 | ||
242 | /** |
242 | /** |
243 | * Decrypt this encrypted node. |
243 | * Decrypt this encrypted node. |
244 | * |
244 | * |
245 | * The behaviour of this function depends on the value of $replace. |
245 | * The behaviour of this function depends on the value of $replace. |
246 | * If $replace is false, we will return the decrypted data as a string. |
246 | * If $replace is false, we will return the decrypted data as a string. |
247 | * If $replace is true, we will insert the decrypted element(s) into the |
247 | * If $replace is true, we will insert the decrypted element(s) into the |
248 | * document, and return the decrypted element(s). |
248 | * document, and return the decrypted element(s). |
249 | * |
249 | * |
250 | * @param XMLSecurityKey $objKey The decryption key that should be used when decrypting the node. |
250 | * @param XMLSecurityKey $objKey The decryption key that should be used when decrypting the node. |
251 | * @param boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true. |
251 | * @param boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true. |
252 | * |
252 | * |
253 | * @return string|DOMElement The decrypted data. |
253 | * @return string|DOMElement The decrypted data. |
254 | */ |
254 | */ |
255 | public function decryptNode($objKey, $replace=true) |
255 | public function decryptNode($objKey, $replace=true) |
256 | { |
256 | { |
257 | if (! $objKey instanceof XMLSecurityKey) { |
257 | if (! $objKey instanceof XMLSecurityKey) { |
258 | throw new Exception('Invalid Key'); |
258 | throw new Exception('Invalid Key'); |
259 | } |
259 | } |
260 | 260 | ||
261 | $encryptedData = $this->getCipherValue(); |
261 | $encryptedData = $this->getCipherValue(); |
262 | if ($encryptedData) { |
262 | if ($encryptedData) { |
263 | $decrypted = $objKey->decryptData($encryptedData); |
263 | $decrypted = $objKey->decryptData($encryptedData); |
264 | if ($replace) { |
264 | if ($replace) { |
265 | switch ($this->type) { |
265 | switch ($this->type) { |
266 | case (self::Element): |
266 | case (self::Element): |
267 | $newdoc = new DOMDocument(); |
267 | $newdoc = new DOMDocument(); |
268 | $newdoc->loadXML($decrypted); |
268 | $newdoc->loadXML($decrypted); |
269 | if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { |
269 | if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { |
270 | return $newdoc; |
270 | return $newdoc; |
271 | } |
271 | } |
272 | $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true); |
272 | $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true); |
273 | $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); |
273 | $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); |
274 | return $importEnc; |
274 | return $importEnc; |
275 | case (self::Content): |
275 | case (self::Content): |
276 | if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { |
276 | if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { |
277 | $doc = $this->rawNode; |
277 | $doc = $this->rawNode; |
278 | } else { |
278 | } else { |
279 | $doc = $this->rawNode->ownerDocument; |
279 | $doc = $this->rawNode->ownerDocument; |
280 | } |
280 | } |
281 | $newFrag = $doc->createDocumentFragment(); |
281 | $newFrag = $doc->createDocumentFragment(); |
282 | $newFrag->appendXML($decrypted); |
282 | $newFrag->appendXML($decrypted); |
283 | $parent = $this->rawNode->parentNode; |
283 | $parent = $this->rawNode->parentNode; |
284 | $parent->replaceChild($newFrag, $this->rawNode); |
284 | $parent->replaceChild($newFrag, $this->rawNode); |
285 | return $parent; |
285 | return $parent; |
286 | default: |
286 | default: |
287 | return $decrypted; |
287 | return $decrypted; |
288 | } |
288 | } |
289 | } else { |
289 | } else { |
290 | return $decrypted; |
290 | return $decrypted; |
291 | } |
291 | } |
292 | } else { |
292 | } else { |
293 | throw new Exception("Cannot locate encrypted data"); |
293 | throw new Exception("Cannot locate encrypted data"); |
294 | } |
294 | } |
295 | } |
295 | } |
296 | 296 | ||
297 | /** |
297 | /** |
298 | * Encrypt the XMLSecurityKey |
298 | * Encrypt the XMLSecurityKey |
299 | * |
299 | * |
300 | * @param XMLSecurityKey $srcKey |
300 | * @param XMLSecurityKey $srcKey |
301 | * @param XMLSecurityKey $rawKey |
301 | * @param XMLSecurityKey $rawKey |
302 | * @param bool $append |
302 | * @param bool $append |
303 | * @throws Exception |
303 | * @throws Exception |
304 | */ |
304 | */ |
305 | public function encryptKey($srcKey, $rawKey, $append=true) |
305 | public function encryptKey($srcKey, $rawKey, $append=true) |
306 | { |
306 | { |
307 | if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) { |
307 | if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) { |
308 | throw new Exception('Invalid Key'); |
308 | throw new Exception('Invalid Key'); |
309 | } |
309 | } |
310 | $strEncKey = base64_encode($srcKey->encryptData($rawKey->key)); |
310 | $strEncKey = base64_encode($srcKey->encryptData($rawKey->key)); |
311 | $root = $this->encdoc->documentElement; |
311 | $root = $this->encdoc->documentElement; |
312 | $encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey'); |
312 | $encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey'); |
313 | if ($append) { |
313 | if ($append) { |
314 | $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild); |
314 | $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild); |
315 | $keyInfo->appendChild($encKey); |
315 | $keyInfo->appendChild($encKey); |
316 | } else { |
316 | } else { |
317 | $this->encKey = $encKey; |
317 | $this->encKey = $encKey; |
318 | } |
318 | } |
319 | $encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); |
319 | $encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); |
320 | $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith()); |
320 | $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith()); |
321 | if (! empty($srcKey->name)) { |
321 | if (! empty($srcKey->name)) { |
322 | $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo')); |
322 | $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo')); |
323 | $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name)); |
323 | $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name)); |
324 | } |
324 | } |
325 | $cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData')); |
325 | $cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData')); |
326 | $cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey)); |
326 | $cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey)); |
327 | if (is_array($this->references) && count($this->references) > 0) { |
327 | if (is_array($this->references) && count($this->references) > 0) { |
328 | $refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList')); |
328 | $refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList')); |
329 | foreach ($this->references AS $name => $reference) { |
329 | foreach ($this->references AS $name => $reference) { |
330 | $refuri = $reference["refuri"]; |
330 | $refuri = $reference["refuri"]; |
331 | $dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference')); |
331 | $dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference')); |
332 | $dataRef->setAttribute("URI", '#' . $refuri); |
332 | $dataRef->setAttribute("URI", '#' . $refuri); |
333 | } |
333 | } |
334 | } |
334 | } |
335 | return; |
335 | return; |
336 | } |
336 | } |
337 | 337 | ||
338 | /** |
338 | /** |
339 | * @param XMLSecurityKey $encKey |
339 | * @param XMLSecurityKey $encKey |
340 | * @return DOMElement|string |
340 | * @return DOMElement|string |
341 | * @throws Exception |
341 | * @throws Exception |
342 | */ |
342 | */ |
343 | public function decryptKey($encKey) |
343 | public function decryptKey($encKey) |
344 | { |
344 | { |
345 | if (! $encKey->isEncrypted) { |
345 | if (! $encKey->isEncrypted) { |
346 | throw new Exception("Key is not Encrypted"); |
346 | throw new Exception("Key is not Encrypted"); |
347 | } |
347 | } |
348 | if (empty($encKey->key)) { |
348 | if (empty($encKey->key)) { |
349 | throw new Exception("Key is missing data to perform the decryption"); |
349 | throw new Exception("Key is missing data to perform the decryption"); |
350 | } |
350 | } |
351 | return $this->decryptNode($encKey, false); |
351 | return $this->decryptNode($encKey, false); |
352 | } |
352 | } |
353 | 353 | ||
354 | /** |
354 | /** |
355 | * @param DOMDocument $element |
355 | * @param DOMDocument $element |
356 | * @return DOMNode|null |
356 | * @return DOMNode|null |
357 | */ |
357 | */ |
358 | public function locateEncryptedData($element) |
358 | public function locateEncryptedData($element) |
359 | { |
359 | { |
360 | if ($element instanceof DOMDocument) { |
360 | if ($element instanceof DOMDocument) { |
361 | $doc = $element; |
361 | $doc = $element; |
362 | } else { |
362 | } else { |
363 | $doc = $element->ownerDocument; |
363 | $doc = $element->ownerDocument; |
364 | } |
364 | } |
365 | if ($doc) { |
365 | if ($doc) { |
366 | $xpath = new DOMXPath($doc); |
366 | $xpath = new DOMXPath($doc); |
367 | $query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']"; |
367 | $query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']"; |
368 | $nodeset = $xpath->query($query); |
368 | $nodeset = $xpath->query($query); |
369 | return $nodeset->item(0); |
369 | return $nodeset->item(0); |
370 | } |
370 | } |
371 | return null; |
371 | return null; |
372 | } |
372 | } |
373 | 373 | ||
374 | /** |
374 | /** |
375 | * Returns the key from the DOM |
375 | * Returns the key from the DOM |
376 | * @param null|DOMNode $node |
376 | * @param null|DOMNode $node |
377 | * @return null|XMLSecurityKey |
377 | * @return null|XMLSecurityKey |
378 | */ |
378 | */ |
379 | public function locateKey($node=null) |
379 | public function locateKey($node=null) |
380 | { |
380 | { |
381 | if (empty($node)) { |
381 | if (empty($node)) { |
382 | $node = $this->rawNode; |
382 | $node = $this->rawNode; |
383 | } |
383 | } |
384 | if (! $node instanceof DOMNode) { |
384 | if (! $node instanceof DOMNode) { |
385 | return null; |
385 | return null; |
386 | } |
386 | } |
387 | if ($doc = $node->ownerDocument) { |
387 | if ($doc = $node->ownerDocument) { |
388 | $xpath = new DOMXPath($doc); |
388 | $xpath = new DOMXPath($doc); |
389 | $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); |
389 | $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); |
390 | $query = ".//xmlsecenc:EncryptionMethod"; |
390 | $query = ".//xmlsecenc:EncryptionMethod"; |
391 | $nodeset = $xpath->query($query, $node); |
391 | $nodeset = $xpath->query($query, $node); |
392 | if ($encmeth = $nodeset->item(0)) { |
392 | if ($encmeth = $nodeset->item(0)) { |
393 | $attrAlgorithm = $encmeth->getAttribute("Algorithm"); |
393 | $attrAlgorithm = $encmeth->getAttribute("Algorithm"); |
394 | try { |
394 | try { |
395 | $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private')); |
395 | $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private')); |
396 | } catch (Exception $e) { |
396 | } catch (\Exception $e) { |
397 | return null; |
397 | return null; |
398 | } |
398 | } |
399 | return $objKey; |
399 | return $objKey; |
400 | } |
400 | } |
401 | } |
401 | } |
402 | return null; |
402 | return null; |
403 | } |
403 | } |
404 | 404 | ||
405 | /** |
405 | /** |
406 | * @param null|XMLSecurityKey $objBaseKey |
406 | * @param null|XMLSecurityKey $objBaseKey |
407 | * @param null|DOMNode $node |
407 | * @param null|DOMNode $node |
408 | * @return null|XMLSecurityKey |
408 | * @return null|XMLSecurityKey |
409 | * @throws Exception |
409 | * @throws Exception |
410 | */ |
410 | */ |
411 | public static function staticLocateKeyInfo($objBaseKey=null, $node=null) |
411 | public static function staticLocateKeyInfo($objBaseKey=null, $node=null) |
412 | { |
412 | { |
413 | if (empty($node) || (! $node instanceof DOMNode)) { |
413 | if (empty($node) || (! $node instanceof DOMNode)) { |
414 | return null; |
414 | return null; |
415 | } |
415 | } |
416 | $doc = $node->ownerDocument; |
416 | $doc = $node->ownerDocument; |
417 | if (!$doc) { |
417 | if (!$doc) { |
418 | return null; |
418 | return null; |
419 | } |
419 | } |
420 | 420 | ||
421 | $xpath = new DOMXPath($doc); |
421 | $xpath = new DOMXPath($doc); |
422 | $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); |
422 | $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); |
423 | $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS); |
423 | $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS); |
424 | $query = "./xmlsecdsig:KeyInfo"; |
424 | $query = "./xmlsecdsig:KeyInfo"; |
425 | $nodeset = $xpath->query($query, $node); |
425 | $nodeset = $xpath->query($query, $node); |
426 | $encmeth = $nodeset->item(0); |
426 | $encmeth = $nodeset->item(0); |
427 | if (!$encmeth) { |
427 | if (!$encmeth) { |
428 | /* No KeyInfo in EncryptedData / EncryptedKey. */ |
428 | /* No KeyInfo in EncryptedData / EncryptedKey. */ |
429 | return $objBaseKey; |
429 | return $objBaseKey; |
430 | } |
430 | } |
431 | 431 | ||
432 | foreach ($encmeth->childNodes AS $child) { |
432 | foreach ($encmeth->childNodes AS $child) { |
433 | switch ($child->localName) { |
433 | switch ($child->localName) { |
434 | case 'KeyName': |
434 | case 'KeyName': |
435 | if (! empty($objBaseKey)) { |
435 | if (! empty($objBaseKey)) { |
436 | $objBaseKey->name = $child->nodeValue; |
436 | $objBaseKey->name = $child->nodeValue; |
437 | } |
437 | } |
438 | break; |
438 | break; |
439 | case 'KeyValue': |
439 | case 'KeyValue': |
440 | foreach ($child->childNodes AS $keyval) { |
440 | foreach ($child->childNodes AS $keyval) { |
441 | switch ($keyval->localName) { |
441 | switch ($keyval->localName) { |
442 | case 'DSAKeyValue': |
442 | case 'DSAKeyValue': |
443 | throw new Exception("DSAKeyValue currently not supported"); |
443 | throw new Exception("DSAKeyValue currently not supported"); |
444 | case 'RSAKeyValue': |
444 | case 'RSAKeyValue': |
445 | $modulus = null; |
445 | $modulus = null; |
446 | $exponent = null; |
446 | $exponent = null; |
447 | if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) { |
447 | if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) { |
448 | $modulus = base64_decode($modulusNode->nodeValue); |
448 | $modulus = base64_decode($modulusNode->nodeValue); |
449 | } |
449 | } |
450 | if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) { |
450 | if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) { |
451 | $exponent = base64_decode($exponentNode->nodeValue); |
451 | $exponent = base64_decode($exponentNode->nodeValue); |
452 | } |
452 | } |
453 | if (empty($modulus) || empty($exponent)) { |
453 | if (empty($modulus) || empty($exponent)) { |
454 | throw new Exception("Missing Modulus or Exponent"); |
454 | throw new Exception("Missing Modulus or Exponent"); |
455 | } |
455 | } |
456 | $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent); |
456 | $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent); |
457 | $objBaseKey->loadKey($publicKey); |
457 | $objBaseKey->loadKey($publicKey); |
458 | break; |
458 | break; |
459 | } |
459 | } |
460 | } |
460 | } |
461 | break; |
461 | break; |
462 | case 'RetrievalMethod': |
462 | case 'RetrievalMethod': |
463 | $type = $child->getAttribute('Type'); |
463 | $type = $child->getAttribute('Type'); |
464 | if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') { |
464 | if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') { |
465 | /* Unsupported key type. */ |
465 | /* Unsupported key type. */ |
466 | break; |
466 | break; |
467 | } |
467 | } |
468 | $uri = $child->getAttribute('URI'); |
468 | $uri = $child->getAttribute('URI'); |
469 | if ($uri[0] !== '#') { |
469 | if ($uri[0] !== '#') { |
470 | /* URI not a reference - unsupported. */ |
470 | /* URI not a reference - unsupported. */ |
471 | break; |
471 | break; |
472 | } |
472 | } |
473 | $id = substr($uri, 1); |
473 | $id = substr($uri, 1); |
474 | 474 | ||
475 | $query = '//xmlsecenc:EncryptedKey[@Id="'.XPath::filterAttrValue($id, XPath::DOUBLE_QUOTE).'"]'; |
475 | $query = '//xmlsecenc:EncryptedKey[@Id="'.XPath::filterAttrValue($id, XPath::DOUBLE_QUOTE).'"]'; |
476 | $keyElement = $xpath->query($query)->item(0); |
476 | $keyElement = $xpath->query($query)->item(0); |
477 | if (!$keyElement) { |
477 | if (!$keyElement) { |
478 | throw new Exception("Unable to locate EncryptedKey with @Id='$id'."); |
478 | throw new Exception("Unable to locate EncryptedKey with @Id='$id'."); |
479 | } |
479 | } |
480 | 480 | ||
481 | return XMLSecurityKey::fromEncryptedKeyElement($keyElement); |
481 | return XMLSecurityKey::fromEncryptedKeyElement($keyElement); |
482 | case 'EncryptedKey': |
482 | case 'EncryptedKey': |
483 | return XMLSecurityKey::fromEncryptedKeyElement($child); |
483 | return XMLSecurityKey::fromEncryptedKeyElement($child); |
484 | case 'X509Data': |
484 | case 'X509Data': |
485 | if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) { |
485 | if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) { |
486 | if ($x509certNodes->length > 0) { |
486 | if ($x509certNodes->length > 0) { |
487 | $x509cert = $x509certNodes->item(0)->textContent; |
487 | $x509cert = $x509certNodes->item(0)->textContent; |
488 | $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert); |
488 | $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert); |
489 | $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; |
489 | $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; |
490 | $objBaseKey->loadKey($x509cert, false, true); |
490 | $objBaseKey->loadKey($x509cert, false, true); |
491 | } |
491 | } |
492 | } |
492 | } |
493 | break; |
493 | break; |
494 | } |
494 | } |
495 | } |
495 | } |
496 | return $objBaseKey; |
496 | return $objBaseKey; |
497 | } |
497 | } |
498 | 498 | ||
499 | /** |
499 | /** |
500 | * @param null|XMLSecurityKey $objBaseKey |
500 | * @param null|XMLSecurityKey $objBaseKey |
501 | * @param null|DOMNode $node |
501 | * @param null|DOMNode $node |
502 | * @return null|XMLSecurityKey |
502 | * @return null|XMLSecurityKey |
503 | */ |
503 | */ |
504 | public function locateKeyInfo($objBaseKey=null, $node=null) |
504 | public function locateKeyInfo($objBaseKey=null, $node=null) |
505 | { |
505 | { |
506 | if (empty($node)) { |
506 | if (empty($node)) { |
507 | $node = $this->rawNode; |
507 | $node = $this->rawNode; |
508 | } |
508 | } |
509 | return self::staticLocateKeyInfo($objBaseKey, $node); |
509 | return self::staticLocateKeyInfo($objBaseKey, $node); |
510 | } |
510 | } |
511 | } |
511 | } |
512 | 512 |