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