Subversion Repositories oidplus

Rev

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

Rev Author Line No. Line
827 daniel-mar 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
 *
874 daniel-mar 13
 * @category  Crypt
14
 * @package   EC
827 daniel-mar 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
 *
874 daniel-mar 36
 * @package EC
827 daniel-mar 37
 * @author  Jim Wigginton <terrafrost@php.net>
874 daniel-mar 38
 * @access  public
827 daniel-mar 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
     *
874 daniel-mar 61
     * @access public
827 daniel-mar 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
}