Rev 846 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
827 | daniel-mar | 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 | * |
||
874 | daniel-mar | 10 | * @category System |
11 | * @package SSH\Agent |
||
827 | daniel-mar | 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 | |||
18 | namespace phpseclib3\System\SSH\Agent; |
||
19 | |||
20 | use phpseclib3\Common\Functions\Strings; |
||
21 | use phpseclib3\Crypt\Common\PrivateKey; |
||
22 | use phpseclib3\Crypt\Common\PublicKey; |
||
23 | use phpseclib3\Crypt\DSA; |
||
24 | use phpseclib3\Crypt\EC; |
||
25 | use phpseclib3\Crypt\RSA; |
||
26 | use phpseclib3\Exception\UnsupportedAlgorithmException; |
||
27 | use 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 | * |
||
874 | daniel-mar | 38 | * @package SSH\Agent |
827 | daniel-mar | 39 | * @author Jim Wigginton <terrafrost@php.net> |
874 | daniel-mar | 40 | * @access internal |
827 | daniel-mar | 41 | */ |
42 | class Identity implements PrivateKey |
||
43 | { |
||
874 | daniel-mar | 44 | use \phpseclib3\System\SSH\Common\Traits\ReadBytes; |
827 | daniel-mar | 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 |
||
874 | daniel-mar | 55 | * @access private |
827 | daniel-mar | 56 | * @see self::getPublicKey() |
57 | */ |
||
58 | private $key; |
||
59 | |||
60 | /** |
||
61 | * Key Blob |
||
62 | * |
||
63 | * @var string |
||
874 | daniel-mar | 64 | * @access private |
827 | daniel-mar | 65 | * @see self::sign() |
66 | */ |
||
67 | private $key_blob; |
||
68 | |||
69 | /** |
||
70 | * Socket Resource |
||
71 | * |
||
72 | * @var resource |
||
874 | daniel-mar | 73 | * @access private |
827 | daniel-mar | 74 | * @see self::sign() |
75 | */ |
||
76 | private $fsock; |
||
77 | |||
78 | /** |
||
79 | * Signature flags |
||
80 | * |
||
81 | * @var int |
||
874 | daniel-mar | 82 | * @access private |
827 | daniel-mar | 83 | * @see self::sign() |
84 | * @see self::setHash() |
||
85 | */ |
||
86 | private $flags = 0; |
||
87 | |||
88 | /** |
||
89 | * Curve Aliases |
||
90 | * |
||
91 | * @var array |
||
874 | daniel-mar | 92 | * @access private |
827 | daniel-mar | 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 |
||
874 | daniel-mar | 105 | * @access private |
827 | daniel-mar | 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 |
||
874 | daniel-mar | 118 | * @access private |
827 | daniel-mar | 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 |
||
874 | daniel-mar | 140 | * @access private |
827 | daniel-mar | 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 |
||
874 | daniel-mar | 156 | * @access public |
827 | daniel-mar | 157 | */ |
158 | public function getPublicKey($type = 'PKCS8') |
||
159 | { |
||
160 | return $this->key; |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * Sets the hash |
||
165 | * |
||
166 | * @param string $hash |
||
874 | daniel-mar | 167 | * @access public |
827 | daniel-mar | 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 |
||
874 | daniel-mar | 221 | * @access public |
827 | daniel-mar | 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 | * |
||
874 | daniel-mar | 239 | * @access public |
827 | daniel-mar | 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 | * |
||
874 | daniel-mar | 259 | * @access public |
827 | daniel-mar | 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 |
||
874 | daniel-mar | 280 | * @access public |
827 | daniel-mar | 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 | * |
||
874 | daniel-mar | 329 | * @access public |
827 | daniel-mar | 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 | } |