[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * XML Formatted EC Key Handler 5 * 6 * More info: 7 * 8 * https://www.w3.org/TR/xmldsig-core/#sec-ECKeyValue 9 * http://en.wikipedia.org/wiki/XML_Signature 10 * 11 * PHP version 5 12 * 13 * @author Jim Wigginton <terrafrost@php.net> 14 * @copyright 2015 Jim Wigginton 15 * @license http://www.opensource.org/licenses/mit-license.html MIT License 16 * @link http://phpseclib.sourceforge.net 17 */ 18 19 namespace phpseclib3\Crypt\EC\Formats\Keys; 20 21 use phpseclib3\Common\Functions\Strings; 22 use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; 23 use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; 24 use phpseclib3\Crypt\EC\BaseCurves\Prime as PrimeCurve; 25 use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; 26 use phpseclib3\Exception\BadConfigurationException; 27 use phpseclib3\Exception\UnsupportedCurveException; 28 use phpseclib3\Math\BigInteger; 29 30 /** 31 * XML Formatted EC Key Handler 32 * 33 * @author Jim Wigginton <terrafrost@php.net> 34 */ 35 abstract class XML 36 { 37 use Common; 38 39 /** 40 * Default namespace 41 * 42 * @var string 43 */ 44 private static $namespace; 45 46 /** 47 * Flag for using RFC4050 syntax 48 * 49 * @var bool 50 */ 51 private static $rfc4050 = false; 52 53 /** 54 * Break a public or private key down into its constituent components 55 * 56 * @param string $key 57 * @param string $password optional 58 * @return array 59 */ 60 public static function load($key, $password = '') 61 { 62 self::initialize_static_variables(); 63 64 if (!Strings::is_stringable($key)) { 65 throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); 66 } 67 68 if (!class_exists('DOMDocument')) { 69 throw new BadConfigurationException('The dom extension is not setup correctly on this system'); 70 } 71 72 $use_errors = libxml_use_internal_errors(true); 73 74 $temp = self::isolateNamespace($key, 'http://www.w3.org/2009/xmldsig11#'); 75 if ($temp) { 76 $key = $temp; 77 } 78 79 $temp = self::isolateNamespace($key, 'http://www.w3.org/2001/04/xmldsig-more#'); 80 if ($temp) { 81 $key = $temp; 82 } 83 84 $dom = new \DOMDocument(); 85 if (substr($key, 0, 5) != '<?xml') { 86 $key = '<xml>' . $key . '</xml>'; 87 } 88 89 if (!$dom->loadXML($key)) { 90 libxml_use_internal_errors($use_errors); 91 throw new \UnexpectedValueException('Key does not appear to contain XML'); 92 } 93 $xpath = new \DOMXPath($dom); 94 libxml_use_internal_errors($use_errors); 95 $curve = self::loadCurveByParam($xpath); 96 97 $pubkey = self::query($xpath, 'publickey', 'Public Key is not present'); 98 99 $QA = self::query($xpath, 'ecdsakeyvalue')->length ? 100 self::extractPointRFC4050($xpath, $curve) : 101 self::extractPoint("\0" . $pubkey, $curve); 102 103 libxml_use_internal_errors($use_errors); 104 105 return compact('curve', 'QA'); 106 } 107 108 /** 109 * Case-insensitive xpath query 110 * 111 * @param \DOMXPath $xpath 112 * @param string $name 113 * @param string $error optional 114 * @param bool $decode optional 115 * @return \DOMNodeList 116 */ 117 private static function query(\DOMXPath $xpath, $name, $error = null, $decode = true) 118 { 119 $query = '/'; 120 $names = explode('/', $name); 121 foreach ($names as $name) { 122 $query .= "/*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$name']"; 123 } 124 $result = $xpath->query($query); 125 if (!isset($error)) { 126 return $result; 127 } 128 129 if (!$result->length) { 130 throw new \RuntimeException($error); 131 } 132 return $decode ? self::decodeValue($result->item(0)->textContent) : $result->item(0)->textContent; 133 } 134 135 /** 136 * Finds the first element in the relevant namespace, strips the namespacing and returns the XML for that element. 137 * 138 * @param string $xml 139 * @param string $ns 140 */ 141 private static function isolateNamespace($xml, $ns) 142 { 143 $dom = new \DOMDocument(); 144 if (!$dom->loadXML($xml)) { 145 return false; 146 } 147 $xpath = new \DOMXPath($dom); 148 $nodes = $xpath->query("//*[namespace::*[.='$ns'] and not(../namespace::*[.='$ns'])]"); 149 if (!$nodes->length) { 150 return false; 151 } 152 $node = $nodes->item(0); 153 $ns_name = $node->lookupPrefix($ns); 154 if ($ns_name) { 155 $node->removeAttributeNS($ns, $ns_name); 156 } 157 return $dom->saveXML($node); 158 } 159 160 /** 161 * Decodes the value 162 * 163 * @param string $value 164 */ 165 private static function decodeValue($value) 166 { 167 return Strings::base64_decode(str_replace(["\r", "\n", ' ', "\t"], '', $value)); 168 } 169 170 /** 171 * Extract points from an XML document 172 * 173 * @param \DOMXPath $xpath 174 * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve 175 * @return object[] 176 */ 177 private static function extractPointRFC4050(\DOMXPath $xpath, BaseCurve $curve) 178 { 179 $x = self::query($xpath, 'publickey/x'); 180 $y = self::query($xpath, 'publickey/y'); 181 if (!$x->length || !$x->item(0)->hasAttribute('Value')) { 182 throw new \RuntimeException('Public Key / X coordinate not found'); 183 } 184 if (!$y->length || !$y->item(0)->hasAttribute('Value')) { 185 throw new \RuntimeException('Public Key / Y coordinate not found'); 186 } 187 $point = [ 188 $curve->convertInteger(new BigInteger($x->item(0)->getAttribute('Value'))), 189 $curve->convertInteger(new BigInteger($y->item(0)->getAttribute('Value'))) 190 ]; 191 if (!$curve->verifyPoint($point)) { 192 throw new \RuntimeException('Unable to verify that point exists on curve'); 193 } 194 return $point; 195 } 196 197 /** 198 * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based 199 * on the curve parameters 200 * 201 * @param \DomXPath $xpath 202 * @return \phpseclib3\Crypt\EC\BaseCurves\Base|false 203 */ 204 private static function loadCurveByParam(\DOMXPath $xpath) 205 { 206 $namedCurve = self::query($xpath, 'namedcurve'); 207 if ($namedCurve->length == 1) { 208 $oid = $namedCurve->item(0)->getAttribute('URN'); 209 $oid = preg_replace('#[^\d.]#', '', $oid); 210 $name = array_search($oid, self::$curveOIDs); 211 if ($name === false) { 212 throw new UnsupportedCurveException('Curve with OID of ' . $oid . ' is not supported'); 213 } 214 215 $curve = '\phpseclib3\Crypt\EC\Curves\\' . $name; 216 if (!class_exists($curve)) { 217 throw new UnsupportedCurveException('Named Curve of ' . $name . ' is not supported'); 218 } 219 return new $curve(); 220 } 221 222 $params = self::query($xpath, 'explicitparams'); 223 if ($params->length) { 224 return self::loadCurveByParamRFC4050($xpath); 225 } 226 227 $params = self::query($xpath, 'ecparameters'); 228 if (!$params->length) { 229 throw new \RuntimeException('No parameters are present'); 230 } 231 232 $fieldTypes = [ 233 'prime-field' => ['fieldid/prime/p'], 234 'gnb' => ['fieldid/gnb/m'], 235 'tnb' => ['fieldid/tnb/k'], 236 'pnb' => ['fieldid/pnb/k1', 'fieldid/pnb/k2', 'fieldid/pnb/k3'], 237 'unknown' => [] 238 ]; 239 240 foreach ($fieldTypes as $type => $queries) { 241 foreach ($queries as $query) { 242 $result = self::query($xpath, $query); 243 if (!$result->length) { 244 continue 2; 245 } 246 $param = preg_replace('#.*/#', '', $query); 247 $$param = self::decodeValue($result->item(0)->textContent); 248 } 249 break; 250 } 251 252 $a = self::query($xpath, 'curve/a', 'A coefficient is not present'); 253 $b = self::query($xpath, 'curve/b', 'B coefficient is not present'); 254 $base = self::query($xpath, 'base', 'Base point is not present'); 255 $order = self::query($xpath, 'order', 'Order is not present'); 256 257 switch ($type) { 258 case 'prime-field': 259 $curve = new PrimeCurve(); 260 $curve->setModulo(new BigInteger($p, 256)); 261 $curve->setCoefficients( 262 new BigInteger($a, 256), 263 new BigInteger($b, 256) 264 ); 265 $point = self::extractPoint("\0" . $base, $curve); 266 $curve->setBasePoint(...$point); 267 $curve->setOrder(new BigInteger($order, 256)); 268 return $curve; 269 case 'gnb': 270 case 'tnb': 271 case 'pnb': 272 default: 273 throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported'); 274 } 275 } 276 277 /** 278 * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based 279 * on the curve parameters 280 * 281 * @param \DomXPath $xpath 282 * @return \phpseclib3\Crypt\EC\BaseCurves\Base|false 283 */ 284 private static function loadCurveByParamRFC4050(\DOMXPath $xpath) 285 { 286 $fieldTypes = [ 287 'prime-field' => ['primefieldparamstype/p'], 288 'unknown' => [] 289 ]; 290 291 foreach ($fieldTypes as $type => $queries) { 292 foreach ($queries as $query) { 293 $result = self::query($xpath, $query); 294 if (!$result->length) { 295 continue 2; 296 } 297 $param = preg_replace('#.*/#', '', $query); 298 $$param = $result->item(0)->textContent; 299 } 300 break; 301 } 302 303 $a = self::query($xpath, 'curveparamstype/a', 'A coefficient is not present', false); 304 $b = self::query($xpath, 'curveparamstype/b', 'B coefficient is not present', false); 305 $x = self::query($xpath, 'basepointparams/basepoint/ecpointtype/x', 'Base Point X is not present', false); 306 $y = self::query($xpath, 'basepointparams/basepoint/ecpointtype/y', 'Base Point Y is not present', false); 307 $order = self::query($xpath, 'order', 'Order is not present', false); 308 309 switch ($type) { 310 case 'prime-field': 311 $curve = new PrimeCurve(); 312 313 $p = str_replace(["\r", "\n", ' ', "\t"], '', $p); 314 $curve->setModulo(new BigInteger($p)); 315 316 $a = str_replace(["\r", "\n", ' ', "\t"], '', $a); 317 $b = str_replace(["\r", "\n", ' ', "\t"], '', $b); 318 $curve->setCoefficients( 319 new BigInteger($a), 320 new BigInteger($b) 321 ); 322 323 $x = str_replace(["\r", "\n", ' ', "\t"], '', $x); 324 $y = str_replace(["\r", "\n", ' ', "\t"], '', $y); 325 $curve->setBasePoint( 326 new BigInteger($x), 327 new BigInteger($y) 328 ); 329 330 $order = str_replace(["\r", "\n", ' ', "\t"], '', $order); 331 $curve->setOrder(new BigInteger($order)); 332 return $curve; 333 default: 334 throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported'); 335 } 336 } 337 338 /** 339 * Sets the namespace. dsig11 is the most common one. 340 * 341 * Set to null to unset. Used only for creating public keys. 342 * 343 * @param string $namespace 344 */ 345 public static function setNamespace($namespace) 346 { 347 self::$namespace = $namespace; 348 } 349 350 /** 351 * Uses the XML syntax specified in https://tools.ietf.org/html/rfc4050 352 */ 353 public static function enableRFC4050Syntax() 354 { 355 self::$rfc4050 = true; 356 } 357 358 /** 359 * Uses the XML syntax specified in https://www.w3.org/TR/xmldsig-core/#sec-ECParameters 360 */ 361 public static function disableRFC4050Syntax() 362 { 363 self::$rfc4050 = false; 364 } 365 366 /** 367 * Convert a public key to the appropriate format 368 * 369 * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve 370 * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey 371 * @param array $options optional 372 * @return string 373 */ 374 public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = []) 375 { 376 self::initialize_static_variables(); 377 378 if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) { 379 throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported'); 380 } 381 382 if (empty(static::$namespace)) { 383 $pre = $post = ''; 384 } else { 385 $pre = static::$namespace . ':'; 386 $post = ':' . static::$namespace; 387 } 388 389 if (self::$rfc4050) { 390 return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2001/04/xmldsig-more#">' . "\r\n" . 391 self::encodeXMLParameters($curve, $pre, $options) . "\r\n" . 392 '<' . $pre . 'PublicKey>' . "\r\n" . 393 '<' . $pre . 'X Value="' . $publicKey[0] . '" />' . "\r\n" . 394 '<' . $pre . 'Y Value="' . $publicKey[1] . '" />' . "\r\n" . 395 '</' . $pre . 'PublicKey>' . "\r\n" . 396 '</' . $pre . 'ECDSAKeyValue>'; 397 } 398 399 $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); 400 401 return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2009/xmldsig11#">' . "\r\n" . 402 self::encodeXMLParameters($curve, $pre, $options) . "\r\n" . 403 '<' . $pre . 'PublicKey>' . Strings::base64_encode($publicKey) . '</' . $pre . 'PublicKey>' . "\r\n" . 404 '</' . $pre . 'ECDSAKeyValue>'; 405 } 406 407 /** 408 * Encode Parameters 409 * 410 * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve 411 * @param string $pre 412 * @param array $options optional 413 * @return string|false 414 */ 415 private static function encodeXMLParameters(BaseCurve $curve, $pre, array $options = []) 416 { 417 $result = self::encodeParameters($curve, true, $options); 418 419 if (isset($result['namedCurve'])) { 420 $namedCurve = '<' . $pre . 'NamedCurve URI="urn:oid:' . self::$curveOIDs[$result['namedCurve']] . '" />'; 421 return self::$rfc4050 ? 422 '<DomainParameters>' . str_replace('URI', 'URN', $namedCurve) . '</DomainParameters>' : 423 $namedCurve; 424 } 425 426 if (self::$rfc4050) { 427 $xml = '<' . $pre . 'ExplicitParams>' . "\r\n" . 428 '<' . $pre . 'FieldParams>' . "\r\n"; 429 $temp = $result['specifiedCurve']; 430 switch ($temp['fieldID']['fieldType']) { 431 case 'prime-field': 432 $xml .= '<' . $pre . 'PrimeFieldParamsType>' . "\r\n" . 433 '<' . $pre . 'P>' . $temp['fieldID']['parameters'] . '</' . $pre . 'P>' . "\r\n" . 434 '</' . $pre . 'PrimeFieldParamsType>' . "\r\n"; 435 $a = $curve->getA(); 436 $b = $curve->getB(); 437 list($x, $y) = $curve->getBasePoint(); 438 break; 439 default: 440 throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported'); 441 } 442 $xml .= '</' . $pre . 'FieldParams>' . "\r\n" . 443 '<' . $pre . 'CurveParamsType>' . "\r\n" . 444 '<' . $pre . 'A>' . $a . '</' . $pre . 'A>' . "\r\n" . 445 '<' . $pre . 'B>' . $b . '</' . $pre . 'B>' . "\r\n" . 446 '</' . $pre . 'CurveParamsType>' . "\r\n" . 447 '<' . $pre . 'BasePointParams>' . "\r\n" . 448 '<' . $pre . 'BasePoint>' . "\r\n" . 449 '<' . $pre . 'ECPointType>' . "\r\n" . 450 '<' . $pre . 'X>' . $x . '</' . $pre . 'X>' . "\r\n" . 451 '<' . $pre . 'Y>' . $y . '</' . $pre . 'Y>' . "\r\n" . 452 '</' . $pre . 'ECPointType>' . "\r\n" . 453 '</' . $pre . 'BasePoint>' . "\r\n" . 454 '<' . $pre . 'Order>' . $curve->getOrder() . '</' . $pre . 'Order>' . "\r\n" . 455 '</' . $pre . 'BasePointParams>' . "\r\n" . 456 '</' . $pre . 'ExplicitParams>' . "\r\n"; 457 458 return $xml; 459 } 460 461 if (isset($result['specifiedCurve'])) { 462 $xml = '<' . $pre . 'ECParameters>' . "\r\n" . 463 '<' . $pre . 'FieldID>' . "\r\n"; 464 $temp = $result['specifiedCurve']; 465 switch ($temp['fieldID']['fieldType']) { 466 case 'prime-field': 467 $xml .= '<' . $pre . 'Prime>' . "\r\n" . 468 '<' . $pre . 'P>' . Strings::base64_encode($temp['fieldID']['parameters']->toBytes()) . '</' . $pre . 'P>' . "\r\n" . 469 '</' . $pre . 'Prime>' . "\r\n" ; 470 break; 471 default: 472 throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported'); 473 } 474 $xml .= '</' . $pre . 'FieldID>' . "\r\n" . 475 '<' . $pre . 'Curve>' . "\r\n" . 476 '<' . $pre . 'A>' . Strings::base64_encode($temp['curve']['a']) . '</' . $pre . 'A>' . "\r\n" . 477 '<' . $pre . 'B>' . Strings::base64_encode($temp['curve']['b']) . '</' . $pre . 'B>' . "\r\n" . 478 '</' . $pre . 'Curve>' . "\r\n" . 479 '<' . $pre . 'Base>' . Strings::base64_encode($temp['base']) . '</' . $pre . 'Base>' . "\r\n" . 480 '<' . $pre . 'Order>' . Strings::base64_encode($temp['order']) . '</' . $pre . 'Order>' . "\r\n" . 481 '</' . $pre . 'ECParameters>'; 482 return $xml; 483 } 484 } 485 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body