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 * Curve Aliases 84 * 85 * @var array 86 */ 87 private static $curveAliases = [ 88 'secp256r1' => 'nistp256', 89 'secp384r1' => 'nistp384', 90 'secp521r1' => 'nistp521', 91 'Ed25519' => 'Ed25519' 92 ]; 93 94 /** 95 * Default Constructor. 96 * 97 * @param resource $fsock 98 */ 99 public function __construct($fsock) 100 { 101 $this->fsock = $fsock; 102 } 103 104 /** 105 * Set Public Key 106 * 107 * Called by \phpseclib3\System\SSH\Agent::requestIdentities() 108 * 109 * @param \phpseclib3\Crypt\Common\PublicKey $key 110 */ 111 public function withPublicKey(PublicKey $key) 112 { 113 if ($key instanceof EC) { 114 if (is_array($key->getCurve()) || !isset(self::$curveAliases[$key->getCurve()])) { 115 throw new UnsupportedAlgorithmException('The only supported curves are nistp256, nistp384, nistp512 and Ed25519'); 116 } 117 } 118 119 $new = clone $this; 120 $new->key = $key; 121 return $new; 122 } 123 124 /** 125 * Set Public Key 126 * 127 * Called by \phpseclib3\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key 128 * but this saves a small amount of computation. 129 * 130 * @param string $key_blob 131 */ 132 public function withPublicKeyBlob($key_blob) 133 { 134 $new = clone $this; 135 $new->key_blob = $key_blob; 136 return $new; 137 } 138 139 /** 140 * Get Public Key 141 * 142 * Wrapper for $this->key->getPublicKey() 143 * 144 * @param string $type optional 145 * @return mixed 146 */ 147 public function getPublicKey($type = 'PKCS8') 148 { 149 return $this->key; 150 } 151 152 /** 153 * Sets the hash 154 * 155 * @param string $hash 156 */ 157 public function withHash($hash) 158 { 159 $new = clone $this; 160 161 $hash = strtolower($hash); 162 163 if ($this->key instanceof RSA) { 164 $new->flags = 0; 165 switch ($hash) { 166 case 'sha1': 167 break; 168 case 'sha256': 169 $new->flags = self::SSH_AGENT_RSA2_256; 170 break; 171 case 'sha512': 172 $new->flags = self::SSH_AGENT_RSA2_512; 173 break; 174 default: 175 throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512'); 176 } 177 } 178 if ($this->key instanceof EC) { 179 switch ($this->key->getCurve()) { 180 case 'secp256r1': 181 $expectedHash = 'sha256'; 182 break; 183 case 'secp384r1': 184 $expectedHash = 'sha384'; 185 break; 186 //case 'secp521r1': 187 //case 'Ed25519': 188 default: 189 $expectedHash = 'sha512'; 190 } 191 if ($hash != $expectedHash) { 192 throw new UnsupportedAlgorithmException('The only supported hash for ' . self::$curveAliases[$this->key->getCurve()] . ' is ' . $expectedHash); 193 } 194 } 195 if ($this->key instanceof DSA) { 196 if ($hash != 'sha1') { 197 throw new UnsupportedAlgorithmException('The only supported hash for DSA is sha1'); 198 } 199 } 200 return $new; 201 } 202 203 /** 204 * Sets the padding 205 * 206 * Only PKCS1 padding is supported 207 * 208 * @param string $padding 209 */ 210 public function withPadding($padding) 211 { 212 if (!$this->key instanceof RSA) { 213 throw new UnsupportedAlgorithmException('Only RSA keys support padding'); 214 } 215 if ($padding != RSA::SIGNATURE_PKCS1 && $padding != RSA::SIGNATURE_RELAXED_PKCS1) { 216 throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures'); 217 } 218 return $this; 219 } 220 221 /** 222 * Determines the signature padding mode 223 * 224 * Valid values are: ASN1, SSH2, Raw 225 * 226 * @param string $format 227 */ 228 public function withSignatureFormat($format) 229 { 230 if ($this->key instanceof RSA) { 231 throw new UnsupportedAlgorithmException('Only DSA and EC keys support signature format setting'); 232 } 233 if ($format != 'SSH2') { 234 throw new UnsupportedAlgorithmException('Only SSH2-formatted signatures are currently supported'); 235 } 236 237 return $this; 238 } 239 240 /** 241 * Returns the curve 242 * 243 * Returns a string if it's a named curve, an array if not 244 * 245 * @return string|array 246 */ 247 public function getCurve() 248 { 249 if (!$this->key instanceof EC) { 250 throw new UnsupportedAlgorithmException('Only EC keys have curves'); 251 } 252 253 return $this->key->getCurve(); 254 } 255 256 /** 257 * Create a signature 258 * 259 * See "2.6.2 Protocol 2 private key signature request" 260 * 261 * @param string $message 262 * @return string 263 * @throws \RuntimeException on connection errors 264 * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported 265 */ 266 public function sign($message) 267 { 268 // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE 269 $packet = Strings::packSSH2( 270 'CssN', 271 Agent::SSH_AGENTC_SIGN_REQUEST, 272 $this->key_blob, 273 $message, 274 $this->flags 275 ); 276 $packet = Strings::packSSH2('s', $packet); 277 if (strlen($packet) != fputs($this->fsock, $packet)) { 278 throw new \RuntimeException('Connection closed during signing'); 279 } 280 281 $length = current(unpack('N', $this->readBytes(4))); 282 $packet = $this->readBytes($length); 283 284 list($type, $signature_blob) = Strings::unpackSSH2('Cs', $packet); 285 if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) { 286 throw new \RuntimeException('Unable to retrieve signature'); 287 } 288 289 if (!$this->key instanceof RSA) { 290 return $signature_blob; 291 } 292 293 list($type, $signature_blob) = Strings::unpackSSH2('ss', $signature_blob); 294 295 return $signature_blob; 296 } 297 298 /** 299 * Returns the private key 300 * 301 * @param string $type 302 * @param array $options optional 303 * @return string 304 */ 305 public function toString($type, array $options = []) 306 { 307 throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); 308 } 309 310 /** 311 * Sets the password 312 * 313 * @param string|bool $password 314 * @return never 315 */ 316 public function withPassword($password = false) 317 { 318 throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); 319 } 320} 321