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