1 2 package ch.ethz.ssh2; 3 4 import java.io.BufferedReader ; 5 import java.io.CharArrayReader ; 6 import java.io.CharArrayWriter ; 7 import java.io.File ; 8 import java.io.FileReader ; 9 import java.io.IOException ; 10 import java.io.RandomAccessFile ; 11 import java.net.InetAddress ; 12 import java.net.UnknownHostException ; 13 import java.security.SecureRandom ; 14 import java.util.Iterator ; 15 import java.util.LinkedList ; 16 import java.util.Vector ; 17 18 import ch.ethz.ssh2.crypto.Base64; 19 import ch.ethz.ssh2.crypto.digest.Digest; 20 import ch.ethz.ssh2.crypto.digest.HMAC; 21 import ch.ethz.ssh2.crypto.digest.MD5; 22 import ch.ethz.ssh2.crypto.digest.SHA1; 23 import ch.ethz.ssh2.signature.DSAPublicKey; 24 import ch.ethz.ssh2.signature.DSASHA1Verify; 25 import ch.ethz.ssh2.signature.RSAPublicKey; 26 import ch.ethz.ssh2.signature.RSASHA1Verify; 27 28 43 44 public class KnownHosts 45 { 46 public static final int HOSTKEY_IS_OK = 0; 47 public static final int HOSTKEY_IS_NEW = 1; 48 public static final int HOSTKEY_HAS_CHANGED = 2; 49 50 private class KnownHostsEntry 51 { 52 String [] patterns; 53 Object key; 54 55 KnownHostsEntry(String [] patterns, Object key) 56 { 57 this.patterns = patterns; 58 this.key = key; 59 } 60 } 61 62 private LinkedList publicKeys = new LinkedList (); 63 64 public KnownHosts() 65 { 66 } 67 68 public KnownHosts(char[] knownHostsData) throws IOException 69 { 70 initialize(knownHostsData); 71 } 72 73 public KnownHosts(File knownHosts) throws IOException 74 { 75 initialize(knownHosts); 76 } 77 78 89 public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException 90 { 91 if (hostnames == null) 92 throw new IllegalArgumentException ("hostnames may not be null"); 93 94 if ("ssh-rsa".equals(serverHostKeyAlgorithm)) 95 { 96 RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); 97 98 synchronized (publicKeys) 99 { 100 publicKeys.add(new KnownHostsEntry(hostnames, rpk)); 101 } 102 } 103 else if ("ssh-dss".equals(serverHostKeyAlgorithm)) 104 { 105 DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); 106 107 synchronized (publicKeys) 108 { 109 publicKeys.add(new KnownHostsEntry(hostnames, dpk)); 110 } 111 } 112 else 113 throw new IOException ("Unknwon host key type (" + serverHostKeyAlgorithm + ")"); 114 } 115 116 122 public void addHostkeys(char[] knownHostsData) throws IOException 123 { 124 initialize(knownHostsData); 125 } 126 127 133 public void addHostkeys(File knownHosts) throws IOException 134 { 135 initialize(knownHosts); 136 } 137 138 145 public static final String createHashedHostname(String hostname) 146 { 147 SHA1 sha1 = new SHA1(); 148 149 byte[] salt = new byte[sha1.getDigestLength()]; 150 151 new SecureRandom ().nextBytes(salt); 152 153 byte[] hash = hmacSha1Hash(salt, hostname); 154 155 String base64_salt = new String (Base64.encode(salt)); 156 String base64_hash = new String (Base64.encode(hash)); 157 158 return new String ("|1|" + base64_salt + "|" + base64_hash); 159 } 160 161 private static final byte[] hmacSha1Hash(byte[] salt, String hostname) 162 { 163 SHA1 sha1 = new SHA1(); 164 165 if (salt.length != sha1.getDigestLength()) 166 throw new IllegalArgumentException ("Salt has wrong length (" + salt.length + ")"); 167 168 HMAC hmac = new HMAC(sha1, salt, salt.length); 169 170 hmac.update(hostname.getBytes()); 171 172 byte[] dig = new byte[hmac.getDigestLength()]; 173 174 hmac.digest(dig); 175 176 return dig; 177 } 178 179 private final boolean checkHashed(String entry, String hostname) 180 { 181 if (entry.startsWith("|1|") == false) 182 return false; 183 184 int delim_idx = entry.indexOf('|', 3); 185 186 if (delim_idx == -1) 187 return false; 188 189 String salt_base64 = entry.substring(3, delim_idx); 190 String hash_base64 = entry.substring(delim_idx + 1); 191 192 byte[] salt = null; 193 byte[] hash = null; 194 195 try 196 { 197 salt = Base64.decode(salt_base64.toCharArray()); 198 hash = Base64.decode(hash_base64.toCharArray()); 199 } 200 catch (IOException e) 201 { 202 return false; 203 } 204 205 SHA1 sha1 = new SHA1(); 206 207 if (salt.length != sha1.getDigestLength()) 208 return false; 209 210 byte[] dig = hmacSha1Hash(salt, hostname); 211 212 for (int i = 0; i < dig.length; i++) 213 if (dig[i] != hash[i]) 214 return false; 215 216 return true; 217 } 218 219 private int checkKey(String remoteHostname, Object remoteKey) 220 { 221 int result = HOSTKEY_IS_NEW; 222 223 synchronized (publicKeys) 224 { 225 Iterator i = publicKeys.iterator(); 226 227 while (i.hasNext()) 228 { 229 KnownHostsEntry ke = (KnownHostsEntry) i.next(); 230 231 if (hostnameMatches(ke.patterns, remoteHostname) == false) 232 continue; 233 234 boolean res = matchKeys(ke.key, remoteKey); 235 236 if (res == true) 237 return HOSTKEY_IS_OK; 238 239 result = HOSTKEY_HAS_CHANGED; 240 } 241 } 242 return result; 243 } 244 245 private Vector getAllKeys(String hostname) 246 { 247 Vector keys = new Vector (); 248 249 synchronized (publicKeys) 250 { 251 Iterator i = publicKeys.iterator(); 252 253 while (i.hasNext()) 254 { 255 KnownHostsEntry ke = (KnownHostsEntry) i.next(); 256 257 if (hostnameMatches(ke.patterns, hostname) == false) 258 continue; 259 260 keys.addElement(ke.key); 261 } 262 } 263 264 return keys; 265 } 266 267 279 public String [] getPreferredServerHostkeyAlgorithmOrder(String hostname) 280 { 281 String [] algos = recommendHostkeyAlgorithms(hostname); 282 283 if (algos != null) 284 return algos; 285 286 InetAddress [] ipAdresses = null; 287 288 try 289 { 290 ipAdresses = InetAddress.getAllByName(hostname); 291 } 292 catch (UnknownHostException e) 293 { 294 return null; 295 } 296 297 for (int i = 0; i < ipAdresses.length; i++) 298 { 299 algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); 300 301 if (algos != null) 302 return algos; 303 } 304 305 return null; 306 } 307 308 private final boolean hostnameMatches(String [] hostpatterns, String hostname) 309 { 310 boolean isMatch = false; 311 boolean negate = false; 312 313 hostname = hostname.toLowerCase(); 314 315 for (int k = 0; k < hostpatterns.length; k++) 316 { 317 if (hostpatterns[k] == null) 318 continue; 319 320 String pattern = null; 321 322 325 326 if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) 327 { 328 pattern = hostpatterns[k].substring(1); 329 negate = true; 330 } 331 else 332 { 333 pattern = hostpatterns[k]; 334 negate = false; 335 } 336 337 338 339 if ((isMatch) && (negate == false)) 340 continue; 341 342 343 344 if (pattern.charAt(0) == '|') 345 { 346 if (checkHashed(pattern, hostname)) 347 { 348 if (negate) 349 return false; 350 isMatch = true; 351 } 352 } 353 else 354 { 355 pattern = pattern.toLowerCase(); 356 357 if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) 358 { 359 if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) 360 { 361 if (negate) 362 return false; 363 isMatch = true; 364 } 365 } 366 else if (pattern.compareTo(hostname) == 0) 367 { 368 if (negate) 369 return false; 370 isMatch = true; 371 } 372 } 373 } 374 375 return isMatch; 376 } 377 378 private void initialize(char[] knownHostsData) throws IOException 379 { 380 BufferedReader br = new BufferedReader (new CharArrayReader (knownHostsData)); 381 382 while (true) 383 { 384 String line = br.readLine(); 385 386 if (line == null) 387 break; 388 389 line = line.trim(); 390 391 if (line.startsWith("#")) 392 continue; 393 394 String [] arr = line.split(" "); 395 396 if (arr.length >= 3) 397 { 398 if ((arr[1].compareTo("ssh-rsa") == 0) || (arr[1].compareTo("ssh-dss") == 0)) 399 { 400 String [] hostnames = arr[0].split(","); 401 402 byte[] msg = Base64.decode(arr[2].toCharArray()); 403 404 addHostkey(hostnames, arr[1], msg); 405 } 406 } 407 } 408 } 409 410 private void initialize(File knownHosts) throws IOException 411 { 412 char[] buff = new char[512]; 413 414 CharArrayWriter cw = new CharArrayWriter (); 415 416 knownHosts.createNewFile(); 417 418 FileReader fr = new FileReader (knownHosts); 419 420 while (true) 421 { 422 int len = fr.read(buff); 423 if (len < 0) 424 break; 425 cw.write(buff, 0, len); 426 } 427 428 fr.close(); 429 430 initialize(cw.toCharArray()); 431 } 432 433 private final boolean matchKeys(Object key1, Object key2) 434 { 435 if ((key1 instanceof RSAPublicKey) && (key2 instanceof RSAPublicKey)) 436 { 437 RSAPublicKey savedRSAKey = (RSAPublicKey) key1; 438 RSAPublicKey remoteRSAKey = (RSAPublicKey) key2; 439 440 if (savedRSAKey.getE().equals(remoteRSAKey.getE()) == false) 441 return false; 442 443 if (savedRSAKey.getN().equals(remoteRSAKey.getN()) == false) 444 return false; 445 446 return true; 447 } 448 449 if ((key1 instanceof DSAPublicKey) && (key2 instanceof DSAPublicKey)) 450 { 451 DSAPublicKey savedDSAKey = (DSAPublicKey) key1; 452 DSAPublicKey remoteDSAKey = (DSAPublicKey) key2; 453 454 if (savedDSAKey.getG().equals(remoteDSAKey.getG()) == false) 455 return false; 456 457 if (savedDSAKey.getP().equals(remoteDSAKey.getP()) == false) 458 return false; 459 460 if (savedDSAKey.getQ().equals(remoteDSAKey.getQ()) == false) 461 return false; 462 463 if (savedDSAKey.getY().equals(remoteDSAKey.getY()) == false) 464 return false; 465 466 return true; 467 } 468 469 return false; 470 } 471 472 private final boolean pseudoRegex(char[] pattern, int i, char[] match, int j) 473 { 474 475 476 while (true) 477 { 478 479 480 if (pattern.length == i) 481 return (match.length == j); 482 483 if (pattern[i] == '*') 484 { 485 i++; 486 487 if (pattern.length == i) 488 return true; 489 490 if ((pattern[i] != '*') && (pattern[i] != '?')) 491 { 492 while (true) 493 { 494 if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) 495 return true; 496 j++; 497 if (match.length == j) 498 return false; 499 } 500 } 501 502 while (true) 503 { 504 if (pseudoRegex(pattern, i, match, j)) 505 return true; 506 j++; 507 if (match.length == j) 508 return false; 509 } 510 } 511 512 if (match.length == j) 513 return false; 514 515 if ((pattern[i] != '?') && (pattern[i] != match[j])) 516 return false; 517 518 i++; 519 j++; 520 } 521 } 522 523 private String [] recommendHostkeyAlgorithms(String hostname) 524 { 525 String preferredAlgo = null; 526 527 Vector keys = getAllKeys(hostname); 528 529 for (int i = 0; i < keys.size(); i++) 530 { 531 String thisAlgo = null; 532 533 if (keys.elementAt(i) instanceof RSAPublicKey) 534 thisAlgo = "ssh-rsa"; 535 else if (keys.elementAt(i) instanceof DSAPublicKey) 536 thisAlgo = "ssh-dss"; 537 else 538 continue; 539 540 if (preferredAlgo != null) 541 { 542 543 544 if (preferredAlgo.compareTo(thisAlgo) != 0) 545 return null; 546 547 548 549 continue; 550 } 551 } 552 553 554 555 if (preferredAlgo == null) 556 return null; 557 558 569 570 if (preferredAlgo.equals("ssh-rsa")) 571 return new String [] { "ssh-rsa", "ssh-dss" }; 572 573 return new String [] { "ssh-dss", "ssh-rsa" }; 574 } 575 576 592 public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException 593 { 594 Object remoteKey = null; 595 596 if ("ssh-rsa".equals(serverHostKeyAlgorithm)) 597 { 598 remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); 599 } 600 else if ("ssh-dss".equals(serverHostKeyAlgorithm)) 601 { 602 remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); 603 } 604 else 605 throw new IllegalArgumentException ("Unknown hostkey type " + serverHostKeyAlgorithm); 606 607 int result = checkKey(hostname, remoteKey); 608 609 if (result == HOSTKEY_IS_OK) 610 return result; 611 612 InetAddress [] ipAdresses = null; 613 614 try 615 { 616 ipAdresses = InetAddress.getAllByName(hostname); 617 } 618 catch (UnknownHostException e) 619 { 620 return result; 621 } 622 623 for (int i = 0; i < ipAdresses.length; i++) 624 { 625 int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); 626 627 if (newresult == HOSTKEY_IS_OK) 628 return newresult; 629 630 if (newresult == HOSTKEY_HAS_CHANGED) 631 result = HOSTKEY_HAS_CHANGED; 632 } 633 634 return result; 635 } 636 637 648 public final static void addHostkeyToFile(File knownHosts, String [] hostnames, String serverHostKeyAlgorithm, 649 byte[] serverHostKey) throws IOException 650 { 651 if ((hostnames == null) || (hostnames.length == 0)) 652 throw new IllegalArgumentException ("Need at least one hostname specification"); 653 654 if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) 655 throw new IllegalArgumentException (); 656 657 CharArrayWriter writer = new CharArrayWriter (); 658 659 for (int i = 0; i < hostnames.length; i++) 660 { 661 if (i != 0) 662 writer.write(','); 663 writer.write(hostnames[i]); 664 } 665 666 writer.write(' '); 667 writer.write(serverHostKeyAlgorithm); 668 writer.write(' '); 669 writer.write(Base64.encode(serverHostKey)); 670 writer.write("\n"); 671 672 char[] entry = writer.toCharArray(); 673 674 RandomAccessFile raf = new RandomAccessFile (knownHosts, "rw"); 675 676 long len = raf.length(); 677 678 if (len > 0) 679 { 680 raf.seek(len - 1); 681 int last = raf.read(); 682 if (last != '\n') 683 raf.write('\n'); 684 } 685 686 raf.write(new String (entry).getBytes()); 687 raf.close(); 688 } 689 690 698 static final private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) 699 { 700 Digest dig = null; 701 702 if ("md5".equals(type)) 703 { 704 dig = new MD5(); 705 } 706 else if ("sha1".equals(type)) 707 { 708 dig = new SHA1(); 709 } 710 else 711 throw new IllegalArgumentException ("Unknown hash type " + type); 712 713 if ("ssh-rsa".equals(keyType)) 714 { 715 } 716 else if ("ssh-dss".equals(keyType)) 717 { 718 } 719 else 720 throw new IllegalArgumentException ("Unknown key type " + keyType); 721 722 if (hostkey == null) 723 throw new IllegalArgumentException ("hostkey is null"); 724 725 dig.update(hostkey); 726 byte[] res = new byte[dig.getDigestLength()]; 727 dig.digest(res); 728 return res; 729 } 730 731 736 static final private String rawToHexFingerprint(byte[] fingerprint) 737 { 738 final char[] alpha = "0123456789abcdef".toCharArray(); 739 740 StringBuffer sb = new StringBuffer (); 741 742 for (int i = 0; i < fingerprint.length; i++) 743 { 744 if (i != 0) 745 sb.append(':'); 746 int b = fingerprint[i] & 0xff; 747 sb.append(alpha[b >> 4]); 748 sb.append(alpha[b & 15]); 749 } 750 751 return sb.toString(); 752 } 753 754 759 static final private String rawToBubblebabbleFingerprint(byte[] raw) 760 { 761 final char[] v = "aeiouy".toCharArray(); 762 final char[] c = "bcdfghklmnprstvzx".toCharArray(); 763 764 StringBuffer sb = new StringBuffer (); 765 766 int seed = 1; 767 768 int rounds = (raw.length / 2) + 1; 769 770 sb.append('x'); 771 772 for (int i = 0; i < rounds; i++) 773 { 774 if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) 775 { 776 sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); 777 sb.append(c[(raw[2 * i] >> 2) & 15]); 778 sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); 779 780 if ((i + 1) < rounds) 781 { 782 sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); 783 sb.append('-'); 784 sb.append(c[(((raw[(2 * i) + 1]))) & 15]); 785 seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; 787 } 788 } 789 else 790 { 791 sb.append(v[seed % 6]); sb.append('x'); 793 sb.append(v[seed / 6]); 794 } 795 } 796 797 sb.append('x'); 798 799 return sb.toString(); 800 } 801 802 812 public final static String createHexFingerprint(String keytype, byte[] publickey) 813 { 814 byte[] raw = rawFingerPrint("md5", keytype, publickey); 815 return rawToHexFingerprint(raw); 816 } 817 818 829 public final static String createBubblebabbleFingerprint(String keytype, byte[] publickey) 830 { 831 byte[] raw = rawFingerPrint("sha1", keytype, publickey); 832 return rawToBubblebabbleFingerprint(raw); 833 } 834 } 835 | Popular Tags |