1<?php 2 3/** 4 * OpenSSH Formatted EC Key Handler 5 * 6 * PHP version 5 7 * 8 * Place in $HOME/.ssh/authorized_keys 9 * 10 * @category Crypt 11 * @package EC 12 * @author Jim Wigginton <terrafrost@php.net> 13 * @copyright 2015 Jim Wigginton 14 * @license http://www.opensource.org/licenses/mit-license.html MIT License 15 * @link http://phpseclib.sourceforge.net 16 */ 17 18namespace phpseclib3\Crypt\EC\Formats\Keys; 19 20use phpseclib3\Common\Functions\Strings; 21use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor; 22use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; 23use phpseclib3\Crypt\EC\Curves\Ed25519; 24use phpseclib3\Exception\UnsupportedCurveException; 25use phpseclib3\Math\BigInteger; 26 27/** 28 * OpenSSH Formatted EC Key Handler 29 * 30 * @package EC 31 * @author Jim Wigginton <terrafrost@php.net> 32 * @access public 33 */ 34abstract class OpenSSH extends Progenitor 35{ 36 use Common; 37 38 /** 39 * Supported Key Types 40 * 41 * @var array 42 */ 43 protected static $types = [ 44 'ecdsa-sha2-nistp256', 45 'ecdsa-sha2-nistp384', 46 'ecdsa-sha2-nistp521', 47 'ssh-ed25519' 48 ]; 49 50 /** 51 * Break a public or private key down into its constituent components 52 * 53 * @access public 54 * @param string $key 55 * @param string $password optional 56 * @return array 57 */ 58 public static function load($key, $password = '') 59 { 60 $parsed = parent::load($key, $password); 61 62 if (isset($parsed['paddedKey'])) { 63 $paddedKey = $parsed['paddedKey']; 64 list($type) = Strings::unpackSSH2('s', $paddedKey); 65 if ($type != $parsed['type']) { 66 throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])"); 67 } 68 if ($type == 'ssh-ed25519') { 69 list(, $key, $comment) = Strings::unpackSSH2('sss', $paddedKey); 70 $key = libsodium::load($key); 71 $key['comment'] = $comment; 72 return $key; 73 } 74 list($curveName, $publicKey, $privateKey, $comment) = Strings::unpackSSH2('ssis', $paddedKey); 75 $curve = self::loadCurveByParam(['namedCurve' => $curveName]); 76 $curve->rangeCheck($privateKey); 77 return [ 78 'curve' => $curve, 79 'dA' => $privateKey, 80 'QA' => self::extractPoint("\0$publicKey", $curve), 81 'comment' => $comment 82 ]; 83 } 84 85 if ($parsed['type'] == 'ssh-ed25519') { 86 if (Strings::shift($parsed['publicKey'], 4) != "\0\0\0\x20") { 87 throw new \RuntimeException('Length of ssh-ed25519 key should be 32'); 88 } 89 90 $curve = new Ed25519(); 91 $qa = self::extractPoint($parsed['publicKey'], $curve); 92 } else { 93 list($curveName, $publicKey) = Strings::unpackSSH2('ss', $parsed['publicKey']); 94 $curveName = '\phpseclib3\Crypt\EC\Curves\\' . $curveName; 95 $curve = new $curveName(); 96 97 $qa = self::extractPoint("\0" . $publicKey, $curve); 98 } 99 100 return [ 101 'curve' => $curve, 102 'QA' => $qa, 103 'comment' => $parsed['comment'] 104 ]; 105 } 106 107 /** 108 * Returns the alias that corresponds to a curve 109 * 110 * @return string 111 */ 112 private static function getAlias(BaseCurve $curve) 113 { 114 self::initialize_static_variables(); 115 116 $reflect = new \ReflectionClass($curve); 117 $name = $reflect->getShortName(); 118 119 $oid = self::$curveOIDs[$name]; 120 $aliases = array_filter(self::$curveOIDs, function ($v) use ($oid) { 121 return $v == $oid; 122 }); 123 $aliases = array_keys($aliases); 124 125 for ($i = 0; $i < count($aliases); $i++) { 126 if (in_array('ecdsa-sha2-' . $aliases[$i], self::$types)) { 127 $alias = $aliases[$i]; 128 break; 129 } 130 } 131 132 if (!isset($alias)) { 133 throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports'); 134 } 135 136 return $alias; 137 } 138 139 /** 140 * Convert an EC public key to the appropriate format 141 * 142 * @access public 143 * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve 144 * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey 145 * @param array $options optional 146 * @return string 147 */ 148 public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = []) 149 { 150 $comment = isset($options['comment']) ? $options['comment'] : self::$comment; 151 152 if ($curve instanceof Ed25519) { 153 $key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey)); 154 155 if (isset($options['binary']) ? $options['binary'] : self::$binary) { 156 return $key; 157 } 158 159 $key = 'ssh-ed25519 ' . base64_encode($key) . ' ' . $comment; 160 return $key; 161 } 162 163 $alias = self::getAlias($curve); 164 165 $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); 166 $key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points); 167 168 if (isset($options['binary']) ? $options['binary'] : self::$binary) { 169 return $key; 170 } 171 172 $key = 'ecdsa-sha2-' . $alias . ' ' . base64_encode($key) . ' ' . $comment; 173 174 return $key; 175 } 176 177 /** 178 * Convert a private key to the appropriate format. 179 * 180 * @access public 181 * @param \phpseclib3\Math\BigInteger $privateKey 182 * @param \phpseclib3\Crypt\EC\Curves\Ed25519 $curve 183 * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey 184 * @param string $password optional 185 * @param array $options optional 186 * @return string 187 */ 188 public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $password = '', array $options = []) 189 { 190 if ($curve instanceof Ed25519) { 191 if (!isset($privateKey->secret)) { 192 throw new \RuntimeException('Private Key does not have a secret set'); 193 } 194 if (strlen($privateKey->secret) != 32) { 195 throw new \RuntimeException('Private Key secret is not of the correct length'); 196 } 197 198 $pubKey = $curve->encodePoint($publicKey); 199 200 $publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey); 201 $privateKey = Strings::packSSH2('sss', 'ssh-ed25519', $pubKey, $privateKey->secret . $pubKey); 202 203 return self::wrapPrivateKey($publicKey, $privateKey, $password, $options); 204 } 205 206 $alias = self::getAlias($curve); 207 208 $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); 209 $publicKey = self::savePublicKey($curve, $publicKey, ['binary' => true]); 210 211 $privateKey = Strings::packSSH2('sssi', 'ecdsa-sha2-' . $alias, $alias, $points, $privateKey); 212 213 return self::wrapPrivateKey($publicKey, $privateKey, $password, $options); 214 } 215} 216