Mercurial > 510Connectbot
comparison src/com/trilead/ssh2/channel/AuthAgentForwardThread.java @ 0:0ce5cc452d02
initial version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 22 May 2014 10:41:19 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:0ce5cc452d02 |
---|---|
1 /* | |
2 * ConnectBot: simple, powerful, open-source SSH client for Android | |
3 * Copyright 2007 Kenny Root, Jeffrey Sharkey | |
4 * | |
5 * Licensed under the Apache License, Version 2.0 (the "License"); | |
6 * you may not use this file except in compliance with the License. | |
7 * You may obtain a copy of the License at | |
8 * | |
9 * http://www.apache.org/licenses/LICENSE-2.0 | |
10 * | |
11 * Unless required by applicable law or agreed to in writing, software | |
12 * distributed under the License is distributed on an "AS IS" BASIS, | |
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 * See the License for the specific language governing permissions and | |
15 * limitations under the License. | |
16 */ | |
17 | |
18 package com.trilead.ssh2.channel; | |
19 | |
20 import java.io.IOException; | |
21 import java.io.InputStream; | |
22 import java.io.OutputStream; | |
23 import java.math.BigInteger; | |
24 import java.security.KeyFactory; | |
25 import java.security.KeyPair; | |
26 import java.security.NoSuchAlgorithmException; | |
27 import java.security.PrivateKey; | |
28 import java.security.PublicKey; | |
29 import java.security.SecureRandom; | |
30 import java.security.interfaces.DSAPrivateKey; | |
31 import java.security.interfaces.RSAPrivateKey; | |
32 import java.security.spec.DSAPrivateKeySpec; | |
33 import java.security.spec.DSAPublicKeySpec; | |
34 import java.security.spec.ECParameterSpec; | |
35 import java.security.spec.ECPoint; | |
36 import java.security.spec.ECPrivateKeySpec; | |
37 import java.security.spec.ECPublicKeySpec; | |
38 import java.security.spec.InvalidKeySpecException; | |
39 import java.security.spec.KeySpec; | |
40 import java.security.spec.RSAPrivateCrtKeySpec; | |
41 import java.security.spec.RSAPublicKeySpec; | |
42 import java.util.Map; | |
43 import java.util.Map.Entry; | |
44 | |
45 import com.trilead.ssh2.AuthAgentCallback; | |
46 import com.trilead.ssh2.log.Logger; | |
47 import com.trilead.ssh2.packets.TypesReader; | |
48 import com.trilead.ssh2.packets.TypesWriter; | |
49 import com.trilead.ssh2.signature.DSASHA1Verify; | |
50 import com.trilead.ssh2.signature.ECDSASHA2Verify; | |
51 import com.trilead.ssh2.signature.RSASHA1Verify; | |
52 | |
53 /** | |
54 * AuthAgentForwardThread. | |
55 * | |
56 * @author Kenny Root | |
57 * @version $Id$ | |
58 */ | |
59 public class AuthAgentForwardThread extends Thread implements IChannelWorkerThread { | |
60 private static final byte[] SSH_AGENT_FAILURE = {0, 0, 0, 1, 5}; // 5 | |
61 private static final byte[] SSH_AGENT_SUCCESS = {0, 0, 0, 1, 6}; // 6 | |
62 | |
63 private static final int SSH2_AGENTC_REQUEST_IDENTITIES = 11; | |
64 private static final int SSH2_AGENT_IDENTITIES_ANSWER = 12; | |
65 | |
66 private static final int SSH2_AGENTC_SIGN_REQUEST = 13; | |
67 private static final int SSH2_AGENT_SIGN_RESPONSE = 14; | |
68 | |
69 private static final int SSH2_AGENTC_ADD_IDENTITY = 17; | |
70 private static final int SSH2_AGENTC_REMOVE_IDENTITY = 18; | |
71 private static final int SSH2_AGENTC_REMOVE_ALL_IDENTITIES = 19; | |
72 | |
73 // private static final int SSH_AGENTC_ADD_SMARTCARD_KEY = 20; | |
74 // private static final int SSH_AGENTC_REMOVE_SMARTCARD_KEY = 21; | |
75 | |
76 private static final int SSH_AGENTC_LOCK = 22; | |
77 private static final int SSH_AGENTC_UNLOCK = 23; | |
78 | |
79 private static final int SSH2_AGENTC_ADD_ID_CONSTRAINED = 25; | |
80 // private static final int SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED = 26; | |
81 | |
82 // Constraints for adding keys | |
83 private static final int SSH_AGENT_CONSTRAIN_LIFETIME = 1; | |
84 private static final int SSH_AGENT_CONSTRAIN_CONFIRM = 2; | |
85 | |
86 // Flags for signature requests | |
87 // private static final int SSH_AGENT_OLD_SIGNATURE = 1; | |
88 | |
89 private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); | |
90 | |
91 AuthAgentCallback authAgent; | |
92 OutputStream os; | |
93 InputStream is; | |
94 Channel c; | |
95 | |
96 byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; | |
97 | |
98 public AuthAgentForwardThread(Channel c, AuthAgentCallback authAgent) { | |
99 this.c = c; | |
100 this.authAgent = authAgent; | |
101 | |
102 if (log.isEnabled()) | |
103 log.log(20, "AuthAgentForwardThread started"); | |
104 } | |
105 | |
106 @Override | |
107 public void run() { | |
108 try { | |
109 c.cm.registerThread(this); | |
110 } | |
111 catch (IOException e) { | |
112 stopWorking(); | |
113 return; | |
114 } | |
115 | |
116 try { | |
117 c.cm.sendOpenConfirmation(c); | |
118 is = c.getStdoutStream(); | |
119 os = c.getStdinStream(); | |
120 int totalSize = 4; | |
121 int readSoFar = 0; | |
122 | |
123 while (true) { | |
124 int len; | |
125 | |
126 try { | |
127 len = is.read(buffer, readSoFar, buffer.length - readSoFar); | |
128 } | |
129 catch (IOException e) { | |
130 stopWorking(); | |
131 return; | |
132 } | |
133 | |
134 if (len <= 0) | |
135 break; | |
136 | |
137 readSoFar += len; | |
138 | |
139 if (readSoFar >= 4) { | |
140 TypesReader tr = new TypesReader(buffer, 0, 4); | |
141 totalSize = tr.readUINT32() + 4; | |
142 } | |
143 | |
144 if (totalSize == readSoFar) { | |
145 TypesReader tr = new TypesReader(buffer, 4, readSoFar - 4); | |
146 int messageType = tr.readByte(); | |
147 | |
148 switch (messageType) { | |
149 case SSH2_AGENTC_REQUEST_IDENTITIES: | |
150 sendIdentities(); | |
151 break; | |
152 | |
153 case SSH2_AGENTC_ADD_IDENTITY: | |
154 addIdentity(tr, false); | |
155 break; | |
156 | |
157 case SSH2_AGENTC_ADD_ID_CONSTRAINED: | |
158 addIdentity(tr, true); | |
159 break; | |
160 | |
161 case SSH2_AGENTC_REMOVE_IDENTITY: | |
162 removeIdentity(tr); | |
163 break; | |
164 | |
165 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: | |
166 removeAllIdentities(tr); | |
167 break; | |
168 | |
169 case SSH2_AGENTC_SIGN_REQUEST: | |
170 processSignRequest(tr); | |
171 break; | |
172 | |
173 case SSH_AGENTC_LOCK: | |
174 processLockRequest(tr); | |
175 break; | |
176 | |
177 case SSH_AGENTC_UNLOCK: | |
178 processUnlockRequest(tr); | |
179 break; | |
180 | |
181 default: | |
182 os.write(SSH_AGENT_FAILURE); | |
183 break; | |
184 } | |
185 | |
186 readSoFar = 0; | |
187 } | |
188 } | |
189 | |
190 c.cm.closeChannel(c, "EOF on both streams reached.", true); | |
191 } | |
192 catch (IOException e) { | |
193 log.log(50, "IOException in agent forwarder: " + e.getMessage()); | |
194 | |
195 try { | |
196 is.close(); | |
197 } | |
198 catch (IOException e1) { | |
199 } | |
200 | |
201 try { | |
202 os.close(); | |
203 } | |
204 catch (IOException e2) { | |
205 } | |
206 | |
207 try { | |
208 c.cm.closeChannel(c, "IOException in agent forwarder (" + e.getMessage() + ")", true); | |
209 } | |
210 catch (IOException e3) { | |
211 } | |
212 } | |
213 } | |
214 | |
215 public void stopWorking() { | |
216 try { | |
217 /* This will lead to an IOException in the is.read() call */ | |
218 is.close(); | |
219 } | |
220 catch (IOException e) { | |
221 } | |
222 } | |
223 | |
224 /** | |
225 * @return whether the agent is locked | |
226 */ | |
227 private boolean failWhenLocked() throws IOException { | |
228 if (authAgent.isAgentLocked()) { | |
229 os.write(SSH_AGENT_FAILURE); | |
230 return true; | |
231 } | |
232 else | |
233 return false; | |
234 } | |
235 | |
236 private void sendIdentities() throws IOException { | |
237 Map<String, byte[]> keys = null; | |
238 TypesWriter tw = new TypesWriter(); | |
239 tw.writeByte(SSH2_AGENT_IDENTITIES_ANSWER); | |
240 int numKeys = 0; | |
241 | |
242 if (!authAgent.isAgentLocked()) | |
243 keys = authAgent.retrieveIdentities(); | |
244 | |
245 if (keys != null) | |
246 numKeys = keys.size(); | |
247 | |
248 tw.writeUINT32(numKeys); | |
249 | |
250 if (keys != null) { | |
251 for (Entry<String, byte[]> entry : keys.entrySet()) { | |
252 byte[] keyBytes = entry.getValue(); | |
253 tw.writeString(keyBytes, 0, keyBytes.length); | |
254 tw.writeString(entry.getKey()); | |
255 } | |
256 } | |
257 | |
258 sendPacket(tw.getBytes()); | |
259 } | |
260 | |
261 /** | |
262 * @param tr | |
263 */ | |
264 private void addIdentity(TypesReader tr, boolean checkConstraints) { | |
265 try { | |
266 if (failWhenLocked()) | |
267 return; | |
268 | |
269 String type = tr.readString(); | |
270 String comment; | |
271 String keyType; | |
272 KeySpec pubSpec; | |
273 KeySpec privSpec; | |
274 | |
275 if (type.equals("ssh-rsa")) { | |
276 keyType = "RSA"; | |
277 BigInteger n = tr.readMPINT(); | |
278 BigInteger e = tr.readMPINT(); | |
279 BigInteger d = tr.readMPINT(); | |
280 BigInteger iqmp = tr.readMPINT(); | |
281 BigInteger p = tr.readMPINT(); | |
282 BigInteger q = tr.readMPINT(); | |
283 comment = tr.readString(); | |
284 // Derive the extra values Java needs. | |
285 BigInteger dmp1 = d.mod(p.subtract(BigInteger.ONE)); | |
286 BigInteger dmq1 = d.mod(q.subtract(BigInteger.ONE)); | |
287 pubSpec = new RSAPublicKeySpec(n, e); | |
288 privSpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, dmp1, dmq1, iqmp); | |
289 } | |
290 else if (type.equals("ssh-dss")) { | |
291 keyType = "DSA"; | |
292 BigInteger p = tr.readMPINT(); | |
293 BigInteger q = tr.readMPINT(); | |
294 BigInteger g = tr.readMPINT(); | |
295 BigInteger y = tr.readMPINT(); | |
296 BigInteger x = tr.readMPINT(); | |
297 comment = tr.readString(); | |
298 pubSpec = new DSAPublicKeySpec(y, p, q, g); | |
299 privSpec = new DSAPrivateKeySpec(x, p, q, g); | |
300 } | |
301 else if (type.equals("ecdsa-sha2-nistp256")) { | |
302 keyType = "EC"; | |
303 String curveName = tr.readString(); | |
304 byte[] groupBytes = tr.readByteString(); | |
305 BigInteger exponent = tr.readMPINT(); | |
306 comment = tr.readString(); | |
307 | |
308 if (!"nistp256".equals(curveName)) { | |
309 log.log(2, "Invalid curve name for ecdsa-sha2-nistp256: " + curveName); | |
310 os.write(SSH_AGENT_FAILURE); | |
311 return; | |
312 } | |
313 | |
314 ECParameterSpec nistp256 = ECDSASHA2Verify.EllipticCurves.nistp256; | |
315 ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, nistp256.getCurve()); | |
316 | |
317 if (group == null) { | |
318 // TODO log error | |
319 os.write(SSH_AGENT_FAILURE); | |
320 return; | |
321 } | |
322 | |
323 pubSpec = new ECPublicKeySpec(group, nistp256); | |
324 privSpec = new ECPrivateKeySpec(exponent, nistp256); | |
325 } | |
326 else { | |
327 log.log(2, "Unknown key type: " + type); | |
328 os.write(SSH_AGENT_FAILURE); | |
329 return; | |
330 } | |
331 | |
332 PublicKey pubKey; | |
333 PrivateKey privKey; | |
334 | |
335 try { | |
336 KeyFactory kf = KeyFactory.getInstance(keyType); | |
337 pubKey = kf.generatePublic(pubSpec); | |
338 privKey = kf.generatePrivate(privSpec); | |
339 } | |
340 catch (NoSuchAlgorithmException ex) { | |
341 // TODO: log error | |
342 os.write(SSH_AGENT_FAILURE); | |
343 return; | |
344 } | |
345 catch (InvalidKeySpecException ex) { | |
346 // TODO: log error | |
347 os.write(SSH_AGENT_FAILURE); | |
348 return; | |
349 } | |
350 | |
351 KeyPair pair = new KeyPair(pubKey, privKey); | |
352 boolean confirmUse = false; | |
353 int lifetime = 0; | |
354 | |
355 if (checkConstraints) { | |
356 while (tr.remain() > 0) { | |
357 int constraint = tr.readByte(); | |
358 | |
359 if (constraint == SSH_AGENT_CONSTRAIN_CONFIRM) | |
360 confirmUse = true; | |
361 else if (constraint == SSH_AGENT_CONSTRAIN_LIFETIME) | |
362 lifetime = tr.readUINT32(); | |
363 else { | |
364 // Unknown constraint. Bail. | |
365 os.write(SSH_AGENT_FAILURE); | |
366 return; | |
367 } | |
368 } | |
369 } | |
370 | |
371 if (authAgent.addIdentity(pair, comment, confirmUse, lifetime)) | |
372 os.write(SSH_AGENT_SUCCESS); | |
373 else | |
374 os.write(SSH_AGENT_FAILURE); | |
375 } | |
376 catch (IOException e) { | |
377 try { | |
378 os.write(SSH_AGENT_FAILURE); | |
379 } | |
380 catch (IOException e1) { | |
381 } | |
382 } | |
383 } | |
384 | |
385 /** | |
386 * @param tr | |
387 */ | |
388 private void removeIdentity(TypesReader tr) { | |
389 try { | |
390 if (failWhenLocked()) | |
391 return; | |
392 | |
393 byte[] publicKey = tr.readByteString(); | |
394 | |
395 if (authAgent.removeIdentity(publicKey)) | |
396 os.write(SSH_AGENT_SUCCESS); | |
397 else | |
398 os.write(SSH_AGENT_FAILURE); | |
399 } | |
400 catch (IOException e) { | |
401 try { | |
402 os.write(SSH_AGENT_FAILURE); | |
403 } | |
404 catch (IOException e1) { | |
405 } | |
406 } | |
407 } | |
408 | |
409 /** | |
410 * @param tr | |
411 */ | |
412 private void removeAllIdentities(TypesReader tr) { | |
413 try { | |
414 if (failWhenLocked()) | |
415 return; | |
416 | |
417 if (authAgent.removeAllIdentities()) | |
418 os.write(SSH_AGENT_SUCCESS); | |
419 else | |
420 os.write(SSH_AGENT_FAILURE); | |
421 } | |
422 catch (IOException e) { | |
423 try { | |
424 os.write(SSH_AGENT_FAILURE); | |
425 } | |
426 catch (IOException e1) { | |
427 } | |
428 } | |
429 } | |
430 | |
431 private void processSignRequest(TypesReader tr) { | |
432 try { | |
433 if (failWhenLocked()) | |
434 return; | |
435 | |
436 byte[] publicKeyBytes = tr.readByteString(); | |
437 byte[] challenge = tr.readByteString(); | |
438 int flags = tr.readUINT32(); | |
439 | |
440 if (flags != 0) { | |
441 // We don't understand any flags; abort! | |
442 os.write(SSH_AGENT_FAILURE); | |
443 return; | |
444 } | |
445 | |
446 KeyPair pair = authAgent.getKeyPair(publicKeyBytes); | |
447 | |
448 if (pair == null) { | |
449 os.write(SSH_AGENT_FAILURE); | |
450 return; | |
451 } | |
452 | |
453 byte[] response; | |
454 PrivateKey privKey = pair.getPrivate(); | |
455 | |
456 if (privKey instanceof RSAPrivateKey) { | |
457 byte[] signature = RSASHA1Verify.generateSignature(challenge, | |
458 (RSAPrivateKey) privKey); | |
459 response = RSASHA1Verify.encodeSSHRSASignature(signature); | |
460 } | |
461 else if (privKey instanceof DSAPrivateKey) { | |
462 byte[] signature = DSASHA1Verify.generateSignature(challenge, | |
463 (DSAPrivateKey) privKey, new SecureRandom()); | |
464 response = DSASHA1Verify.encodeSSHDSASignature(signature); | |
465 } | |
466 else { | |
467 os.write(SSH_AGENT_FAILURE); | |
468 return; | |
469 } | |
470 | |
471 TypesWriter tw = new TypesWriter(); | |
472 tw.writeByte(SSH2_AGENT_SIGN_RESPONSE); | |
473 tw.writeString(response, 0, response.length); | |
474 sendPacket(tw.getBytes()); | |
475 } | |
476 catch (IOException e) { | |
477 try { | |
478 os.write(SSH_AGENT_FAILURE); | |
479 } | |
480 catch (IOException e1) { | |
481 } | |
482 } | |
483 } | |
484 | |
485 /** | |
486 * @param tr | |
487 */ | |
488 private void processLockRequest(TypesReader tr) { | |
489 try { | |
490 if (failWhenLocked()) | |
491 return; | |
492 | |
493 String lockPassphrase = tr.readString(); | |
494 | |
495 if (!authAgent.setAgentLock(lockPassphrase)) { | |
496 os.write(SSH_AGENT_FAILURE); | |
497 return; | |
498 } | |
499 else | |
500 os.write(SSH_AGENT_SUCCESS); | |
501 } | |
502 catch (IOException e) { | |
503 try { | |
504 os.write(SSH_AGENT_FAILURE); | |
505 } | |
506 catch (IOException e1) { | |
507 } | |
508 } | |
509 } | |
510 | |
511 /** | |
512 * @param tr | |
513 */ | |
514 private void processUnlockRequest(TypesReader tr) { | |
515 try { | |
516 String unlockPassphrase = tr.readString(); | |
517 | |
518 if (authAgent.requestAgentUnlock(unlockPassphrase)) | |
519 os.write(SSH_AGENT_SUCCESS); | |
520 else | |
521 os.write(SSH_AGENT_FAILURE); | |
522 } | |
523 catch (IOException e) { | |
524 try { | |
525 os.write(SSH_AGENT_FAILURE); | |
526 } | |
527 catch (IOException e1) { | |
528 } | |
529 } | |
530 } | |
531 | |
532 /** | |
533 * @param tw | |
534 * @throws IOException | |
535 */ | |
536 private void sendPacket(byte[] message) throws IOException { | |
537 TypesWriter packet = new TypesWriter(); | |
538 packet.writeUINT32(message.length); | |
539 packet.writeBytes(message); | |
540 os.write(packet.getBytes()); | |
541 } | |
542 } |