Subversion Repositories oidplus

Rev

Rev 846 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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