1<?php 2 3/** 4 * PKCS#8 Formatted EC Key Handler 5 * 6 * PHP version 5 7 * 8 * Processes keys with the following headers: 9 * 10 * -----BEGIN ENCRYPTED PRIVATE KEY----- 11 * -----BEGIN PRIVATE KEY----- 12 * -----BEGIN PUBLIC KEY----- 13 * 14 * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8 15 * is specific to private keys it's basically creating a DER-encoded wrapper 16 * for keys. This just extends that same concept to public keys (much like ssh-keygen) 17 * 18 * @category Crypt 19 * @package EC 20 * @author Jim Wigginton <terrafrost@php.net> 21 * @copyright 2015 Jim Wigginton 22 * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 * @link http://phpseclib.sourceforge.net 24 */ 25 26namespace phpseclib3\Crypt\EC\Formats\Keys; 27 28use phpseclib3\Common\Functions\Strings; 29use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; 30use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; 31use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; 32use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; 33use phpseclib3\Crypt\EC\Curves\Ed25519; 34use phpseclib3\Crypt\EC\Curves\Ed448; 35use phpseclib3\Exception\UnsupportedCurveException; 36use phpseclib3\File\ASN1; 37use phpseclib3\File\ASN1\Maps; 38use phpseclib3\Math\BigInteger; 39 40/** 41 * PKCS#8 Formatted EC Key Handler 42 * 43 * @package EC 44 * @author Jim Wigginton <terrafrost@php.net> 45 * @access public 46 */ 47abstract class PKCS8 extends Progenitor 48{ 49 use Common; 50 51 /** 52 * OID Name 53 * 54 * @var array 55 * @access private 56 */ 57 const OID_NAME = ['id-ecPublicKey', 'id-Ed25519', 'id-Ed448']; 58 59 /** 60 * OID Value 61 * 62 * @var string 63 * @access private 64 */ 65 const OID_VALUE = ['1.2.840.10045.2.1', '1.3.101.112', '1.3.101.113']; 66 67 /** 68 * Break a public or private key down into its constituent components 69 * 70 * @access public 71 * @param string $key 72 * @param string $password optional 73 * @return array 74 */ 75 public static function load($key, $password = '') 76 { 77 // initialize_static_variables() is defined in both the trait and the parent class 78 // when it's defined in two places it's the traits one that's called 79 // the parent one is needed, as well, but the parent one is called by other methods 80 // in the parent class as needed and in the context of the parent it's the parent 81 // one that's called 82 self::initialize_static_variables(); 83 84 if (!Strings::is_stringable($key)) { 85 throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); 86 } 87 88 $isPublic = strpos($key, 'PUBLIC') !== false; 89 90 $key = parent::load($key, $password); 91 92 $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey'; 93 94 switch (true) { 95 case !$isPublic && $type == 'publicKey': 96 throw new \UnexpectedValueException('Human readable string claims non-public key but DER encoded string claims public key'); 97 case $isPublic && $type == 'privateKey': 98 throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key'); 99 } 100 101 switch ($key[$type . 'Algorithm']['algorithm']) { 102 case 'id-Ed25519': 103 case 'id-Ed448': 104 return self::loadEdDSA($key); 105 } 106 107 $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element); 108 $params = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP); 109 if (!$params) { 110 throw new \RuntimeException('Unable to decode the parameters using Maps\ECParameters'); 111 } 112 113 $components = []; 114 $components['curve'] = self::loadCurveByParam($params); 115 116 if ($isPublic) { 117 $components['QA'] = self::extractPoint("\0" . $key['publicKey'], $components['curve']); 118 119 return $components; 120 } 121 122 $decoded = ASN1::decodeBER($key['privateKey']); 123 $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP); 124 if (isset($key['parameters']) && $params != $key['parameters']) { 125 throw new \RuntimeException('The PKCS8 parameter field does not match the private key parameter field'); 126 } 127 128 $components['dA'] = new BigInteger($key['privateKey'], 256); 129 $components['curve']->rangeCheck($components['dA']); 130 $components['QA'] = isset($key['publicKey']) ? 131 self::extractPoint($key['publicKey'], $components['curve']) : 132 $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); 133 134 return $components; 135 } 136 137 /** 138 * Break a public or private EdDSA key down into its constituent components 139 * 140 * @return array 141 */ 142 private static function loadEdDSA(array $key) 143 { 144 $components = []; 145 146 if (isset($key['privateKey'])) { 147 $components['curve'] = $key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448(); 148 149 // 0x04 == octet string 150 // 0x20 == length (32 bytes) 151 if (substr($key['privateKey'], 0, 2) != "\x04\x20") { 152 throw new \RuntimeException('The first two bytes of the private key field should be 0x0420'); 153 } 154 $components['dA'] = $components['curve']->extractSecret(substr($key['privateKey'], 2)); 155 } 156 157 if (isset($key['publicKey'])) { 158 if (!isset($components['curve'])) { 159 $components['curve'] = $key['publicKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448(); 160 } 161 162 $components['QA'] = self::extractPoint($key['publicKey'], $components['curve']); 163 } 164 165 if (isset($key['privateKey']) && !isset($components['QA'])) { 166 $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); 167 } 168 169 return $components; 170 } 171 172 /** 173 * Convert an EC public key to the appropriate format 174 * 175 * @access public 176 * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve 177 * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey 178 * @param array $options optional 179 * @return string 180 */ 181 public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = []) 182 { 183 self::initialize_static_variables(); 184 185 if ($curve instanceof MontgomeryCurve) { 186 throw new UnsupportedCurveException('Montgomery Curves are not supported'); 187 } 188 189 if ($curve instanceof TwistedEdwardsCurve) { 190 return self::wrapPublicKey( 191 $curve->encodePoint($publicKey), 192 null, 193 $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448' 194 ); 195 } 196 197 $params = new ASN1\Element(self::encodeParameters($curve, false, $options)); 198 199 $key = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); 200 201 return self::wrapPublicKey($key, $params, 'id-ecPublicKey'); 202 } 203 204 /** 205 * Convert a private key to the appropriate format. 206 * 207 * @access public 208 * @param \phpseclib3\Math\BigInteger $privateKey 209 * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve 210 * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey 211 * @param string $password optional 212 * @param array $options optional 213 * @return string 214 */ 215 public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $password = '', array $options = []) 216 { 217 self::initialize_static_variables(); 218 219 if ($curve instanceof MontgomeryCurve) { 220 throw new UnsupportedCurveException('Montgomery Curves are not supported'); 221 } 222 223 if ($curve instanceof TwistedEdwardsCurve) { 224 return self::wrapPrivateKey( 225 "\x04\x20" . $privateKey->secret, 226 [], 227 null, 228 $password, 229 $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448' 230 ); 231 } 232 233 $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); 234 235 $params = new ASN1\Element(self::encodeParameters($curve, false, $options)); 236 237 $key = [ 238 'version' => 'ecPrivkeyVer1', 239 'privateKey' => $privateKey->toBytes(), 240 //'parameters' => $params, 241 'publicKey' => "\0" . $publicKey 242 ]; 243 244 $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP); 245 246 return self::wrapPrivateKey($key, [], $params, $password, 'id-ecPublicKey', '', $options); 247 } 248} 249