001: package ch.ethz.ssh2.transport;
002:
003: import java.io.IOException;
004: import java.security.SecureRandom;
005:
006: import ch.ethz.ssh2.ConnectionInfo;
007: import ch.ethz.ssh2.DHGexParameters;
008: import ch.ethz.ssh2.ServerHostKeyVerifier;
009: import ch.ethz.ssh2.crypto.CryptoWishList;
010: import ch.ethz.ssh2.crypto.KeyMaterial;
011: import ch.ethz.ssh2.crypto.cipher.BlockCipher;
012: import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory;
013: import ch.ethz.ssh2.crypto.dh.DhExchange;
014: import ch.ethz.ssh2.crypto.dh.DhGroupExchange;
015: import ch.ethz.ssh2.crypto.digest.MAC;
016: import ch.ethz.ssh2.log.Logger;
017: import ch.ethz.ssh2.packets.PacketKexDHInit;
018: import ch.ethz.ssh2.packets.PacketKexDHReply;
019: import ch.ethz.ssh2.packets.PacketKexDhGexGroup;
020: import ch.ethz.ssh2.packets.PacketKexDhGexInit;
021: import ch.ethz.ssh2.packets.PacketKexDhGexReply;
022: import ch.ethz.ssh2.packets.PacketKexDhGexRequest;
023: import ch.ethz.ssh2.packets.PacketKexDhGexRequestOld;
024: import ch.ethz.ssh2.packets.PacketKexInit;
025: import ch.ethz.ssh2.packets.PacketNewKeys;
026: import ch.ethz.ssh2.packets.Packets;
027: import ch.ethz.ssh2.signature.DSAPublicKey;
028: import ch.ethz.ssh2.signature.DSASHA1Verify;
029: import ch.ethz.ssh2.signature.DSASignature;
030: import ch.ethz.ssh2.signature.RSAPublicKey;
031: import ch.ethz.ssh2.signature.RSASHA1Verify;
032: import ch.ethz.ssh2.signature.RSASignature;
033:
034: /**
035: * KexManager.
036: *
037: * @author Christian Plattner, plattner@inf.ethz.ch
038: * @version $Id: KexManager.java,v 1.11 2006/09/20 12:51:37 cplattne Exp $
039: */
040: public class KexManager {
041: private static final Logger log = Logger
042: .getLogger(KexManager.class);
043:
044: KexState kxs;
045: int kexCount = 0;
046: KeyMaterial km;
047: byte[] sessionId;
048: ClientServerHello csh;
049:
050: final Object accessLock = new Object();
051: ConnectionInfo lastConnInfo = null;
052:
053: boolean connectionClosed = false;
054:
055: boolean ignore_next_kex_packet = false;
056:
057: final TransportManager tm;
058:
059: CryptoWishList nextKEXcryptoWishList;
060: DHGexParameters nextKEXdhgexParameters;
061:
062: ServerHostKeyVerifier verifier;
063: final String hostname;
064: final int port;
065: final SecureRandom rnd;
066:
067: public KexManager(TransportManager tm, ClientServerHello csh,
068: CryptoWishList initialCwl, String hostname, int port,
069: ServerHostKeyVerifier keyVerifier, SecureRandom rnd) {
070: this .tm = tm;
071: this .csh = csh;
072: this .nextKEXcryptoWishList = initialCwl;
073: this .nextKEXdhgexParameters = new DHGexParameters();
074: this .hostname = hostname;
075: this .port = port;
076: this .verifier = keyVerifier;
077: this .rnd = rnd;
078: }
079:
080: public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount)
081: throws IOException {
082: synchronized (accessLock) {
083: while (true) {
084: if ((lastConnInfo != null)
085: && (lastConnInfo.keyExchangeCounter >= minKexCount))
086: return lastConnInfo;
087:
088: if (connectionClosed)
089: throw (IOException) new IOException(
090: "Key exchange was not finished, connection is closed.")
091: .initCause(tm.getReasonClosedCause());
092:
093: try {
094: accessLock.wait();
095: } catch (InterruptedException e) {
096: }
097: }
098: }
099: }
100:
101: private String getFirstMatch(String[] client, String[] server)
102: throws NegotiateException {
103: if (client == null || server == null)
104: throw new IllegalArgumentException();
105:
106: if (client.length == 0)
107: return null;
108:
109: for (int i = 0; i < client.length; i++) {
110: for (int j = 0; j < server.length; j++) {
111: if (client[i].equals(server[j]))
112: return client[i];
113: }
114: }
115: throw new NegotiateException();
116: }
117:
118: private boolean compareFirstOfNameList(String[] a, String[] b) {
119: if (a == null || b == null)
120: throw new IllegalArgumentException();
121:
122: if ((a.length == 0) && (b.length == 0))
123: return true;
124:
125: if ((a.length == 0) || (b.length == 0))
126: return false;
127:
128: return (a[0].equals(b[0]));
129: }
130:
131: private boolean isGuessOK(KexParameters cpar, KexParameters spar) {
132: if (cpar == null || spar == null)
133: throw new IllegalArgumentException();
134:
135: if (compareFirstOfNameList(cpar.kex_algorithms,
136: spar.kex_algorithms) == false) {
137: return false;
138: }
139:
140: if (compareFirstOfNameList(cpar.server_host_key_algorithms,
141: spar.server_host_key_algorithms) == false) {
142: return false;
143: }
144:
145: /*
146: * We do NOT check here if the other algorithms can be agreed on, this
147: * is just a check if kex_algorithms and server_host_key_algorithms were
148: * guessed right!
149: */
150:
151: return true;
152: }
153:
154: private NegotiatedParameters mergeKexParameters(
155: KexParameters client, KexParameters server) {
156: NegotiatedParameters np = new NegotiatedParameters();
157:
158: try {
159: np.kex_algo = getFirstMatch(client.kex_algorithms,
160: server.kex_algorithms);
161:
162: log.log(20, "kex_algo=" + np.kex_algo);
163:
164: np.server_host_key_algo = getFirstMatch(
165: client.server_host_key_algorithms,
166: server.server_host_key_algorithms);
167:
168: log.log(20, "server_host_key_algo="
169: + np.server_host_key_algo);
170:
171: np.enc_algo_client_to_server = getFirstMatch(
172: client.encryption_algorithms_client_to_server,
173: server.encryption_algorithms_client_to_server);
174: np.enc_algo_server_to_client = getFirstMatch(
175: client.encryption_algorithms_server_to_client,
176: server.encryption_algorithms_server_to_client);
177:
178: log.log(20, "enc_algo_client_to_server="
179: + np.enc_algo_client_to_server);
180: log.log(20, "enc_algo_server_to_client="
181: + np.enc_algo_server_to_client);
182:
183: np.mac_algo_client_to_server = getFirstMatch(
184: client.mac_algorithms_client_to_server,
185: server.mac_algorithms_client_to_server);
186: np.mac_algo_server_to_client = getFirstMatch(
187: client.mac_algorithms_server_to_client,
188: server.mac_algorithms_server_to_client);
189:
190: log.log(20, "mac_algo_client_to_server="
191: + np.mac_algo_client_to_server);
192: log.log(20, "mac_algo_server_to_client="
193: + np.mac_algo_server_to_client);
194:
195: np.comp_algo_client_to_server = getFirstMatch(
196: client.compression_algorithms_client_to_server,
197: server.compression_algorithms_client_to_server);
198: np.comp_algo_server_to_client = getFirstMatch(
199: client.compression_algorithms_server_to_client,
200: server.compression_algorithms_server_to_client);
201:
202: log.log(20, "comp_algo_client_to_server="
203: + np.comp_algo_client_to_server);
204: log.log(20, "comp_algo_server_to_client="
205: + np.comp_algo_server_to_client);
206:
207: } catch (NegotiateException e) {
208: return null;
209: }
210:
211: try {
212: np.lang_client_to_server = getFirstMatch(
213: client.languages_client_to_server,
214: server.languages_client_to_server);
215: } catch (NegotiateException e1) {
216: np.lang_client_to_server = null;
217: }
218:
219: try {
220: np.lang_server_to_client = getFirstMatch(
221: client.languages_server_to_client,
222: server.languages_server_to_client);
223: } catch (NegotiateException e2) {
224: np.lang_server_to_client = null;
225: }
226:
227: if (isGuessOK(client, server))
228: np.guessOK = true;
229:
230: return np;
231: }
232:
233: public synchronized void initiateKEX(CryptoWishList cwl,
234: DHGexParameters dhgex) throws IOException {
235: nextKEXcryptoWishList = cwl;
236: nextKEXdhgexParameters = dhgex;
237:
238: if (kxs == null) {
239: kxs = new KexState();
240:
241: kxs.dhgexParameters = nextKEXdhgexParameters;
242: PacketKexInit kp = new PacketKexInit(nextKEXcryptoWishList,
243: rnd);
244: kxs.localKEX = kp;
245: tm.sendKexMessage(kp.getPayload());
246: }
247: }
248:
249: private boolean establishKeyMaterial() {
250: try {
251: int mac_cs_key_len = MAC
252: .getKeyLen(kxs.np.mac_algo_client_to_server);
253: int enc_cs_key_len = BlockCipherFactory
254: .getKeySize(kxs.np.enc_algo_client_to_server);
255: int enc_cs_block_len = BlockCipherFactory
256: .getBlockSize(kxs.np.enc_algo_client_to_server);
257:
258: int mac_sc_key_len = MAC
259: .getKeyLen(kxs.np.mac_algo_server_to_client);
260: int enc_sc_key_len = BlockCipherFactory
261: .getKeySize(kxs.np.enc_algo_server_to_client);
262: int enc_sc_block_len = BlockCipherFactory
263: .getBlockSize(kxs.np.enc_algo_server_to_client);
264:
265: km = KeyMaterial.create("SHA1", kxs.H, kxs.K, sessionId,
266: enc_cs_key_len, enc_cs_block_len, mac_cs_key_len,
267: enc_sc_key_len, enc_sc_block_len, mac_sc_key_len);
268: } catch (IllegalArgumentException e) {
269: return false;
270: }
271: return true;
272: }
273:
274: private void finishKex() throws IOException {
275: if (sessionId == null)
276: sessionId = kxs.H;
277:
278: establishKeyMaterial();
279:
280: /* Tell the other side that we start using the new material */
281:
282: PacketNewKeys ign = new PacketNewKeys();
283: tm.sendKexMessage(ign.getPayload());
284:
285: BlockCipher cbc;
286: MAC mac;
287:
288: try {
289: cbc = BlockCipherFactory.createCipher(
290: kxs.np.enc_algo_client_to_server, true,
291: km.enc_key_client_to_server,
292: km.initial_iv_client_to_server);
293:
294: mac = new MAC(kxs.np.mac_algo_client_to_server,
295: km.integrity_key_client_to_server);
296:
297: } catch (IllegalArgumentException e1) {
298: throw new IOException("Fatal error during MAC startup!");
299: }
300:
301: tm.changeSendCipher(cbc, mac);
302: tm.kexFinished();
303: }
304:
305: public static final String[] getDefaultServerHostkeyAlgorithmList() {
306: return new String[] { "ssh-rsa", "ssh-dss" };
307: }
308:
309: public static final void checkServerHostkeyAlgorithmsList(
310: String[] algos) {
311: for (int i = 0; i < algos.length; i++) {
312: if (("ssh-rsa".equals(algos[i]) == false)
313: && ("ssh-dss".equals(algos[i]) == false))
314: throw new IllegalArgumentException(
315: "Unknown server host key algorithm '"
316: + algos[i] + "'");
317: }
318: }
319:
320: public static final String[] getDefaultKexAlgorithmList() {
321: return new String[] { "diffie-hellman-group-exchange-sha1",
322: "diffie-hellman-group14-sha1",
323: "diffie-hellman-group1-sha1" };
324: }
325:
326: public static final void checkKexAlgorithmList(String[] algos) {
327: for (int i = 0; i < algos.length; i++) {
328: if ("diffie-hellman-group-exchange-sha1".equals(algos[i]))
329: continue;
330:
331: if ("diffie-hellman-group14-sha1".equals(algos[i]))
332: continue;
333:
334: if ("diffie-hellman-group1-sha1".equals(algos[i]))
335: continue;
336:
337: throw new IllegalArgumentException(
338: "Unknown kex algorithm '" + algos[i] + "'");
339: }
340: }
341:
342: private boolean verifySignature(byte[] sig, byte[] hostkey)
343: throws IOException {
344: if (kxs.np.server_host_key_algo.equals("ssh-rsa")) {
345: RSASignature rs = RSASHA1Verify.decodeSSHRSASignature(sig);
346: RSAPublicKey rpk = RSASHA1Verify
347: .decodeSSHRSAPublicKey(hostkey);
348:
349: log.log(50, "Verifying ssh-rsa signature");
350:
351: return RSASHA1Verify.verifySignature(kxs.H, rs, rpk);
352: }
353:
354: if (kxs.np.server_host_key_algo.equals("ssh-dss")) {
355: DSASignature ds = DSASHA1Verify.decodeSSHDSASignature(sig);
356: DSAPublicKey dpk = DSASHA1Verify
357: .decodeSSHDSAPublicKey(hostkey);
358:
359: log.log(50, "Verifying ssh-dss signature");
360:
361: return DSASHA1Verify.verifySignature(kxs.H, ds, dpk);
362: }
363:
364: throw new IOException("Unknown server host key algorithm '"
365: + kxs.np.server_host_key_algo + "'");
366: }
367:
368: public synchronized void handleMessage(byte[] msg, int msglen)
369: throws IOException {
370: PacketKexInit kip;
371:
372: if (msg == null) {
373: synchronized (accessLock) {
374: connectionClosed = true;
375: accessLock.notifyAll();
376: return;
377: }
378: }
379:
380: if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT))
381: throw new IOException("Unexpected KEX message (type "
382: + msg[0] + ")");
383:
384: if (ignore_next_kex_packet) {
385: ignore_next_kex_packet = false;
386: return;
387: }
388:
389: if (msg[0] == Packets.SSH_MSG_KEXINIT) {
390: if ((kxs != null) && (kxs.state != 0))
391: throw new IOException(
392: "Unexpected SSH_MSG_KEXINIT message during on-going kex exchange!");
393:
394: if (kxs == null) {
395: /*
396: * Ah, OK, peer wants to do KEX. Let's be nice and play
397: * together.
398: */
399: kxs = new KexState();
400: kxs.dhgexParameters = nextKEXdhgexParameters;
401: kip = new PacketKexInit(nextKEXcryptoWishList, rnd);
402: kxs.localKEX = kip;
403: tm.sendKexMessage(kip.getPayload());
404: }
405:
406: kip = new PacketKexInit(msg, 0, msglen);
407: kxs.remoteKEX = kip;
408:
409: kxs.np = mergeKexParameters(
410: kxs.localKEX.getKexParameters(), kxs.remoteKEX
411: .getKexParameters());
412:
413: if (kxs.np == null)
414: throw new IOException(
415: "Cannot negotiate, proposals do not match.");
416:
417: if (kxs.remoteKEX.isFirst_kex_packet_follows()
418: && (kxs.np.guessOK == false)) {
419: /*
420: * Guess was wrong, we need to ignore the next kex packet.
421: */
422:
423: ignore_next_kex_packet = true;
424: }
425:
426: if (kxs.np.kex_algo
427: .equals("diffie-hellman-group-exchange-sha1")) {
428: if (kxs.dhgexParameters.getMin_group_len() == 0) {
429: PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(
430: kxs.dhgexParameters);
431: tm.sendKexMessage(dhgexreq.getPayload());
432:
433: } else {
434: PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(
435: kxs.dhgexParameters);
436: tm.sendKexMessage(dhgexreq.getPayload());
437: }
438: kxs.state = 1;
439: return;
440: }
441:
442: if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")
443: || kxs.np.kex_algo
444: .equals("diffie-hellman-group14-sha1")) {
445: kxs.dhx = new DhExchange();
446:
447: if (kxs.np.kex_algo
448: .equals("diffie-hellman-group1-sha1"))
449: kxs.dhx.init(1, rnd);
450: else
451: kxs.dhx.init(14, rnd);
452:
453: PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE());
454: tm.sendKexMessage(kp.getPayload());
455: kxs.state = 1;
456: return;
457: }
458:
459: throw new IllegalStateException("Unkown KEX method!");
460: }
461:
462: if (msg[0] == Packets.SSH_MSG_NEWKEYS) {
463: if (km == null)
464: throw new IOException(
465: "Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!");
466:
467: BlockCipher cbc;
468: MAC mac;
469:
470: try {
471: cbc = BlockCipherFactory.createCipher(
472: kxs.np.enc_algo_server_to_client, false,
473: km.enc_key_server_to_client,
474: km.initial_iv_server_to_client);
475:
476: mac = new MAC(kxs.np.mac_algo_server_to_client,
477: km.integrity_key_server_to_client);
478:
479: } catch (IllegalArgumentException e1) {
480: throw new IOException("Fatal error during MAC startup!");
481: }
482:
483: tm.changeRecvCipher(cbc, mac);
484:
485: ConnectionInfo sci = new ConnectionInfo();
486:
487: kexCount++;
488:
489: sci.keyExchangeAlgorithm = kxs.np.kex_algo;
490: sci.keyExchangeCounter = kexCount;
491: sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server;
492: sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client;
493: sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server;
494: sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client;
495: sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo;
496: sci.serverHostKey = kxs.hostkey;
497:
498: synchronized (accessLock) {
499: lastConnInfo = sci;
500: accessLock.notifyAll();
501: }
502:
503: kxs = null;
504: return;
505: }
506:
507: if ((kxs == null) || (kxs.state == 0))
508: throw new IOException("Unexpected Kex submessage!");
509:
510: if (kxs.np.kex_algo
511: .equals("diffie-hellman-group-exchange-sha1")) {
512: if (kxs.state == 1) {
513: PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(
514: msg, 0, msglen);
515: kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(),
516: dhgexgrp.getG());
517: kxs.dhgx.init(rnd);
518: PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(
519: kxs.dhgx.getE());
520: tm.sendKexMessage(dhgexinit.getPayload());
521: kxs.state = 2;
522: return;
523: }
524:
525: if (kxs.state == 2) {
526: PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(
527: msg, 0, msglen);
528:
529: kxs.hostkey = dhgexrpl.getHostKey();
530:
531: if (verifier != null) {
532: boolean vres = false;
533:
534: try {
535: vres = verifier.verifyServerHostKey(hostname,
536: port, kxs.np.server_host_key_algo,
537: kxs.hostkey);
538: } catch (Exception e) {
539: throw (IOException) new IOException(
540: "The server hostkey was not accepted by the verifier callback.")
541: .initCause(e);
542: }
543:
544: if (vres == false)
545: throw new IOException(
546: "The server hostkey was not accepted by the verifier callback");
547: }
548:
549: kxs.dhgx.setF(dhgexrpl.getF());
550:
551: try {
552: kxs.H = kxs.dhgx.calculateH(csh.getClientString(),
553: csh.getServerString(), kxs.localKEX
554: .getPayload(), kxs.remoteKEX
555: .getPayload(), dhgexrpl
556: .getHostKey(), kxs.dhgexParameters);
557: } catch (IllegalArgumentException e) {
558: throw (IOException) new IOException("KEX error.")
559: .initCause(e);
560: }
561:
562: boolean res = verifySignature(dhgexrpl.getSignature(),
563: kxs.hostkey);
564:
565: if (res == false)
566: throw new IOException(
567: "Hostkey signature sent by remote is wrong!");
568:
569: kxs.K = kxs.dhgx.getK();
570:
571: finishKex();
572: kxs.state = -1;
573: return;
574: }
575:
576: throw new IllegalStateException(
577: "Illegal State in KEX Exchange!");
578: }
579:
580: if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")
581: || kxs.np.kex_algo
582: .equals("diffie-hellman-group14-sha1")) {
583: if (kxs.state == 1) {
584:
585: PacketKexDHReply dhr = new PacketKexDHReply(msg, 0,
586: msglen);
587:
588: kxs.hostkey = dhr.getHostKey();
589:
590: if (verifier != null) {
591: boolean vres = false;
592:
593: try {
594: vres = verifier.verifyServerHostKey(hostname,
595: port, kxs.np.server_host_key_algo,
596: kxs.hostkey);
597: } catch (Exception e) {
598: throw (IOException) new IOException(
599: "The server hostkey was not accepted by the verifier callback.")
600: .initCause(e);
601: }
602:
603: if (vres == false)
604: throw new IOException(
605: "The server hostkey was not accepted by the verifier callback");
606: }
607:
608: kxs.dhx.setF(dhr.getF());
609:
610: try {
611: kxs.H = kxs.dhx.calculateH(csh.getClientString(),
612: csh.getServerString(), kxs.localKEX
613: .getPayload(), kxs.remoteKEX
614: .getPayload(), dhr.getHostKey());
615: } catch (IllegalArgumentException e) {
616: throw (IOException) new IOException("KEX error.")
617: .initCause(e);
618: }
619:
620: boolean res = verifySignature(dhr.getSignature(),
621: kxs.hostkey);
622:
623: if (res == false)
624: throw new IOException(
625: "Hostkey signature sent by remote is wrong!");
626:
627: kxs.K = kxs.dhx.getK();
628:
629: finishKex();
630: kxs.state = -1;
631: return;
632: }
633: }
634:
635: throw new IllegalStateException("Unkown KEX method! ("
636: + kxs.np.kex_algo + ")");
637: }
638: }
|