1<?php 2 3/** 4 * Pure-PHP ssh-agent client. 5 * 6 * {@internal See http://api.libssh.org/rfc/PROTOCOL.agent} 7 * 8 * PHP version 5 9 * 10 * @category System 11 * @package SSH\Agent 12 * @author Jim Wigginton <terrafrost@php.net> 13 * @copyright 2009 Jim Wigginton 14 * @license http://www.opensource.org/licenses/mit-license.html MIT License 15 * @link http://phpseclib.sourceforge.net 16 */ 17 18namespace phpseclib3\System\SSH\Agent; 19 20use phpseclib3\Common\Functions\Strings; 21use phpseclib3\Crypt\Common\PrivateKey; 22use phpseclib3\Crypt\Common\PublicKey; 23use phpseclib3\Crypt\DSA; 24use phpseclib3\Crypt\EC; 25use phpseclib3\Crypt\RSA; 26use phpseclib3\Exception\UnsupportedAlgorithmException; 27use phpseclib3\System\SSH\Agent; 28 29/** 30 * Pure-PHP ssh-agent client identity object 31 * 32 * Instantiation should only be performed by \phpseclib3\System\SSH\Agent class. 33 * This could be thought of as implementing an interface that phpseclib3\Crypt\RSA 34 * implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something. 35 * The methods in this interface would be getPublicKey and sign since those are the 36 * methods phpseclib looks for to perform public key authentication. 37 * 38 * @package SSH\Agent 39 * @author Jim Wigginton <terrafrost@php.net> 40 * @access internal 41 */ 42class Identity implements PrivateKey 43{ 44 use \phpseclib3\System\SSH\Common\Traits\ReadBytes; 45 46 // Signature Flags 47 // See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3 48 const SSH_AGENT_RSA2_256 = 2; 49 const SSH_AGENT_RSA2_512 = 4; 50 51 /** 52 * Key Object 53 * 54 * @var PublicKey 55 * @access private 56 * @see self::getPublicKey() 57 */ 58 private $key; 59 60 /** 61 * Key Blob 62 * 63 * @var string 64 * @access private 65 * @see self::sign() 66 */ 67 private $key_blob; 68 69 /** 70 * Socket Resource 71 * 72 * @var resource 73 * @access private 74 * @see self::sign() 75 */ 76 private $fsock; 77 78 /** 79 * Signature flags 80 * 81 * @var int 82 * @access private 83 * @see self::sign() 84 * @see self::setHash() 85 */ 86 private $flags = 0; 87 88 /** 89 * Curve Aliases 90 * 91 * @var array 92 * @access private 93 */ 94 private static $curveAliases = [ 95 'secp256r1' => 'nistp256', 96 'secp384r1' => 'nistp384', 97 'secp521r1' => 'nistp521', 98 'Ed25519' => 'Ed25519' 99 ]; 100 101 /** 102 * Default Constructor. 103 * 104 * @param resource $fsock 105 * @access private 106 */ 107 public function __construct($fsock) 108 { 109 $this->fsock = $fsock; 110 } 111 112 /** 113 * Set Public Key 114 * 115 * Called by \phpseclib3\System\SSH\Agent::requestIdentities() 116 * 117 * @param \phpseclib3\Crypt\Common\PublicKey $key 118 * @access private 119 */ 120 public function withPublicKey($key) 121 { 122 if ($key instanceof EC) { 123 if (is_array($key->getCurve()) || !isset(self::$curveAliases[$key->getCurve()])) { 124 throw new UnsupportedAlgorithmException('The only supported curves are nistp256, nistp384, nistp512 and Ed25519'); 125 } 126 } 127 128 $new = clone $this; 129 $new->key = $key; 130 return $new; 131 } 132 133 /** 134 * Set Public Key 135 * 136 * Called by \phpseclib3\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key 137 * but this saves a small amount of computation. 138 * 139 * @param string $key_blob 140 * @access private 141 */ 142 public function withPublicKeyBlob($key_blob) 143 { 144 $new = clone $this; 145 $new->key_blob = $key_blob; 146 return $new; 147 } 148 149 /** 150 * Get Public Key 151 * 152 * Wrapper for $this->key->getPublicKey() 153 * 154 * @param string $type optional 155 * @return mixed 156 * @access public 157 */ 158 public function getPublicKey($type = 'PKCS8') 159 { 160 return $this->key; 161 } 162 163 /** 164 * Sets the hash 165 * 166 * @param string $hash 167 * @access public 168 */ 169 public function withHash($hash) 170 { 171 $new = clone $this; 172 173 $hash = strtolower($hash); 174 175 if ($this->key instanceof RSA) { 176 $new->flags = 0; 177 switch ($hash) { 178 case 'sha1': 179 break; 180 case 'sha256': 181 $new->flags = self::SSH_AGENT_RSA2_256; 182 break; 183 case 'sha512': 184 $new->flags = self::SSH_AGENT_RSA2_512; 185 break; 186 default: 187 throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512'); 188 } 189 } 190 if ($this->key instanceof EC) { 191 switch ($this->key->getCurve()) { 192 case 'secp256r1': 193 $expectedHash = 'sha256'; 194 break; 195 case 'secp384r1': 196 $expectedHash = 'sha384'; 197 break; 198 //case 'secp521r1': 199 //case 'Ed25519': 200 default: 201 $expectedHash = 'sha512'; 202 } 203 if ($hash != $expectedHash) { 204 throw new UnsupportedAlgorithmException('The only supported hash for ' . self::$curveAliases[$this->key->getCurve()] . ' is ' . $expectedHash); 205 } 206 } 207 if ($this->key instanceof DSA) { 208 if ($hash != 'sha1') { 209 throw new UnsupportedAlgorithmException('The only supported hash for DSA is sha1'); 210 } 211 } 212 return $new; 213 } 214 215 /** 216 * Sets the padding 217 * 218 * Only PKCS1 padding is supported 219 * 220 * @param string $padding 221 * @access public 222 */ 223 public function withPadding($padding) 224 { 225 if (!$this->key instanceof RSA) { 226 throw new UnsupportedAlgorithmException('Only RSA keys support padding'); 227 } 228 if ($padding != RSA::SIGNATURE_PKCS1 && $padding != RSA::SIGNATURE_RELAXED_PKCS1) { 229 throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures'); 230 } 231 return $this; 232 } 233 234 /** 235 * Determines the signature padding mode 236 * 237 * Valid values are: ASN1, SSH2, Raw 238 * 239 * @access public 240 * @param string $format 241 */ 242 public function withSignatureFormat($format) 243 { 244 if ($this->key instanceof RSA) { 245 throw new UnsupportedAlgorithmException('Only DSA and EC keys support signature format setting'); 246 } 247 if ($format != 'SSH2') { 248 throw new UnsupportedAlgorithmException('Only SSH2-formatted signatures are currently supported'); 249 } 250 251 return $this; 252 } 253 254 /** 255 * Returns the curve 256 * 257 * Returns a string if it's a named curve, an array if not 258 * 259 * @access public 260 * @return string|array 261 */ 262 public function getCurve() 263 { 264 if (!$this->key instanceof EC) { 265 throw new UnsupportedAlgorithmException('Only EC keys have curves'); 266 } 267 268 return $this->key->getCurve(); 269 } 270 271 /** 272 * Create a signature 273 * 274 * See "2.6.2 Protocol 2 private key signature request" 275 * 276 * @param string $message 277 * @return string 278 * @throws \RuntimeException on connection errors 279 * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported 280 * @access public 281 */ 282 public function sign($message) 283 { 284 // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE 285 $packet = Strings::packSSH2( 286 'CssN', 287 Agent::SSH_AGENTC_SIGN_REQUEST, 288 $this->key_blob, 289 $message, 290 $this->flags 291 ); 292 $packet = Strings::packSSH2('s', $packet); 293 if (strlen($packet) != fputs($this->fsock, $packet)) { 294 throw new \RuntimeException('Connection closed during signing'); 295 } 296 297 $length = current(unpack('N', $this->readBytes(4))); 298 $packet = $this->readBytes($length); 299 300 list($type, $signature_blob) = Strings::unpackSSH2('Cs', $packet); 301 if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) { 302 throw new \RuntimeException('Unable to retrieve signature'); 303 } 304 305 if (!$this->key instanceof RSA) { 306 return $signature_blob; 307 } 308 309 list($type, $signature_blob) = Strings::unpackSSH2('ss', $signature_blob); 310 311 return $signature_blob; 312 } 313 314 /** 315 * Returns the private key 316 * 317 * @param string $type 318 * @param array $options optional 319 * @return string 320 */ 321 public function toString($type, array $options = []) 322 { 323 throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); 324 } 325 326 /** 327 * Sets the password 328 * 329 * @access public 330 * @param string|bool $password 331 * @return never 332 */ 333 public function withPassword($password = false) 334 { 335 throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); 336 } 337} 338