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