1<?php
2
3/**
4 * "PKCS1" (RFC5915) Formatted EC Key Handler
5 *
6 * PHP version 5
7 *
8 * Used by File/X509.php
9 *
10 * Processes keys with the following headers:
11 *
12 * -----BEGIN EC PRIVATE KEY-----
13 * -----BEGIN EC PARAMETERS-----
14 *
15 * Technically, PKCS1 is for RSA keys, only, but we're using PKCS1 to describe
16 * DSA, whose format isn't really formally described anywhere, so might as well
17 * use it to describe this, too. PKCS1 is easier to remember than RFC5915, after
18 * all. I suppose this could also be named IETF but idk
19 *
20 * @category  Crypt
21 * @package   EC
22 * @author    Jim Wigginton <terrafrost@php.net>
23 * @copyright 2015 Jim Wigginton
24 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
25 * @link      http://phpseclib.sourceforge.net
26 */
27
28namespace phpseclib3\Crypt\EC\Formats\Keys;
29
30use ParagonIE\ConstantTime\Base64;
31use phpseclib3\Common\Functions\Strings;
32use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
33use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
34use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
35use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
36use phpseclib3\Exception\UnsupportedCurveException;
37use phpseclib3\File\ASN1;
38use phpseclib3\File\ASN1\Maps;
39use phpseclib3\Math\BigInteger;
40
41/**
42 * "PKCS1" (RFC5915) Formatted EC Key Handler
43 *
44 * @package EC
45 * @author  Jim Wigginton <terrafrost@php.net>
46 * @access  public
47 */
48abstract class PKCS1 extends Progenitor
49{
50    use Common;
51
52    /**
53     * Break a public or private key down into its constituent components
54     *
55     * @access public
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 (strpos($key, 'BEGIN EC PARAMETERS') && strpos($key, 'BEGIN EC PRIVATE KEY')) {
69            $components = [];
70
71            preg_match('#-*BEGIN EC PRIVATE KEY-*[^-]*-*END EC PRIVATE KEY-*#s', $key, $matches);
72            $decoded = parent::load($matches[0], $password);
73            $decoded = ASN1::decodeBER($decoded);
74            if (empty($decoded)) {
75                throw new \RuntimeException('Unable to decode BER');
76            }
77
78            $ecPrivate = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
79            if (!is_array($ecPrivate)) {
80                throw new \RuntimeException('Unable to perform ASN1 mapping');
81            }
82
83            if (isset($ecPrivate['parameters'])) {
84                $components['curve'] = self::loadCurveByParam($ecPrivate['parameters']);
85            }
86
87            preg_match('#-*BEGIN EC PARAMETERS-*[^-]*-*END EC PARAMETERS-*#s', $key, $matches);
88            $decoded = parent::load($matches[0], '');
89            $decoded = ASN1::decodeBER($decoded);
90            if (empty($decoded)) {
91                throw new \RuntimeException('Unable to decode BER');
92            }
93            $ecParams = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
94            if (!is_array($ecParams)) {
95                throw new \RuntimeException('Unable to perform ASN1 mapping');
96            }
97            $ecParams = self::loadCurveByParam($ecParams);
98
99            // comparing $ecParams and $components['curve'] directly won't work because they'll have different Math\Common\FiniteField classes
100            // even if the modulo is the same
101            if (isset($components['curve']) && self::encodeParameters($ecParams, false, []) != self::encodeParameters($components['curve'], false, [])) {
102                throw new \RuntimeException('EC PARAMETERS does not correspond to EC PRIVATE KEY');
103            }
104
105            if (!isset($components['curve'])) {
106                $components['curve'] = $ecParams;
107            }
108
109            $components['dA'] = new BigInteger($ecPrivate['privateKey'], 256);
110            $components['curve']->rangeCheck($components['dA']);
111            $components['QA'] = isset($ecPrivate['publicKey']) ?
112                self::extractPoint($ecPrivate['publicKey'], $components['curve']) :
113                $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
114
115            return $components;
116        }
117
118        $key = parent::load($key, $password);
119
120        $decoded = ASN1::decodeBER($key);
121        if (empty($decoded)) {
122            throw new \RuntimeException('Unable to decode BER');
123        }
124
125        $key = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
126        if (is_array($key)) {
127            return ['curve' => self::loadCurveByParam($key)];
128        }
129
130        $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
131        if (!is_array($key)) {
132            throw new \RuntimeException('Unable to perform ASN1 mapping');
133        }
134        if (!isset($key['parameters'])) {
135            throw new \RuntimeException('Key cannot be loaded without parameters');
136        }
137
138        $components = [];
139        $components['curve'] = self::loadCurveByParam($key['parameters']);
140        $components['dA'] = new BigInteger($key['privateKey'], 256);
141        $components['QA'] = isset($ecPrivate['publicKey']) ?
142            self::extractPoint($ecPrivate['publicKey'], $components['curve']) :
143            $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
144
145        return $components;
146    }
147
148    /**
149     * Convert EC parameters to the appropriate format
150     *
151     * @access public
152     * @return string
153     */
154    public static function saveParameters(BaseCurve $curve, array $options = [])
155    {
156        self::initialize_static_variables();
157
158        if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) {
159            throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported');
160        }
161
162        $key = self::encodeParameters($curve, false, $options);
163
164        return "-----BEGIN EC PARAMETERS-----\r\n" .
165               chunk_split(Base64::encode($key), 64) .
166               "-----END EC PARAMETERS-----\r\n";
167    }
168
169    /**
170     * Convert a private key to the appropriate format.
171     *
172     * @access public
173     * @param \phpseclib3\Math\BigInteger $privateKey
174     * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve
175     * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
176     * @param string $password optional
177     * @param array $options optional
178     * @return string
179     */
180    public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $password = '', array $options = [])
181    {
182        self::initialize_static_variables();
183
184        if ($curve instanceof TwistedEdwardsCurve  || $curve instanceof MontgomeryCurve) {
185            throw new UnsupportedCurveException('TwistedEdwards Curves are not supported');
186        }
187
188        $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
189
190        $key = [
191            'version' => 'ecPrivkeyVer1',
192            'privateKey' => $privateKey->toBytes(),
193            'parameters' => new ASN1\Element(self::encodeParameters($curve)),
194            'publicKey' => "\0" . $publicKey
195        ];
196
197        $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP);
198
199        return self::wrapPrivateKey($key, 'EC', $password, $options);
200    }
201}
202