[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/ -> XML.php (source)

   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  }