comparison src/ch/ethz/ssh2/KnownHosts.java @ 308:42b15aaa7ac7 ganymed

merge
author Carl Byington <carl@five-ten-sg.com>
date Wed, 30 Jul 2014 14:21:50 -0700
parents 071eccdff8ea
children b40bc65fa09a
comparison
equal deleted inserted replaced
306:90e47d99ea54 308:42b15aaa7ac7
94 * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. 94 * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}.
95 * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. 95 * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
96 * @throws IOException 96 * @throws IOException
97 */ 97 */
98 public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { 98 public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException {
99 if(hostnames == null) { 99 if (hostnames == null) {
100 throw new IllegalArgumentException("hostnames may not be null"); 100 throw new IllegalArgumentException("hostnames may not be null");
101 } 101 }
102 102
103 if("ssh-rsa".equals(serverHostKeyAlgorithm)) { 103 if ("ssh-rsa".equals(serverHostKeyAlgorithm)) {
104 RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); 104 RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey);
105 105
106 synchronized(publicKeys) { 106 synchronized (publicKeys) {
107 publicKeys.add(new KnownHostsEntry(hostnames, rpk)); 107 publicKeys.add(new KnownHostsEntry(hostnames, rpk));
108 } 108 }
109 } 109 }
110 else if("ssh-dss".equals(serverHostKeyAlgorithm)) { 110 else if ("ssh-dss".equals(serverHostKeyAlgorithm)) {
111 DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); 111 DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey);
112 112
113 synchronized(publicKeys) { 113 synchronized (publicKeys) {
114 publicKeys.add(new KnownHostsEntry(hostnames, dpk)); 114 publicKeys.add(new KnownHostsEntry(hostnames, dpk));
115 } 115 }
116 } 116 }
117 else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) { 117 else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) {
118 ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); 118 ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey);
119 119
120 synchronized(publicKeys) { 120 synchronized (publicKeys) {
121 publicKeys.add(new KnownHostsEntry(hostnames, epk)); 121 publicKeys.add(new KnownHostsEntry(hostnames, epk));
122 } 122 }
123 } 123 }
124 else { 124 else {
125 throw new IOException(String.format("Unknown host key type %s", serverHostKeyAlgorithm)); 125 throw new IOException(String.format("Unknown host key type %s", serverHostKeyAlgorithm));
153 * @param hostname 153 * @param hostname
154 * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA=" 154 * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA="
155 */ 155 */
156 public static String createHashedHostname(String hostname) throws IOException { 156 public static String createHashedHostname(String hostname) throws IOException {
157 SHA1 sha1 = new SHA1(); 157 SHA1 sha1 = new SHA1();
158
159 byte[] salt = new byte[sha1.getDigestLength()]; 158 byte[] salt = new byte[sha1.getDigestLength()];
160
161 new SecureRandom().nextBytes(salt); 159 new SecureRandom().nextBytes(salt);
162
163 byte[] hash; 160 byte[] hash;
161
164 try { 162 try {
165 hash = hmacSha1Hash(salt, hostname); 163 hash = hmacSha1Hash(salt, hostname);
166 } 164 }
167 catch(IOException e) { 165 catch (IOException e) {
168 throw new IOException(e); 166 throw new IOException(e);
169 } 167 }
170 168
171 String base64_salt = new String(Base64.encode(salt)); 169 String base64_salt = new String(Base64.encode(salt));
172 String base64_hash = new String(Base64.encode(hash)); 170 String base64_hash = new String(Base64.encode(hash));
173
174 return String.format("|1|%s|%s", base64_salt, base64_hash); 171 return String.format("|1|%s|%s", base64_salt, base64_hash);
175 } 172 }
176 173
177 private static byte[] hmacSha1Hash(byte[] salt, String hostname) throws IOException { 174 private static byte[] hmacSha1Hash(byte[] salt, String hostname) throws IOException {
178 SHA1 sha1 = new SHA1(); 175 SHA1 sha1 = new SHA1();
179 176
180 if(salt.length != sha1.getDigestLength()) { 177 if (salt.length != sha1.getDigestLength()) {
181 throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")"); 178 throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")");
182 } 179 }
180
183 try { 181 try {
184 HMAC hmac = new HMAC(sha1, salt, salt.length); 182 HMAC hmac = new HMAC(sha1, salt, salt.length);
185
186 hmac.update(StringEncoder.GetBytes(hostname)); 183 hmac.update(StringEncoder.GetBytes(hostname));
187
188 byte[] dig = new byte[hmac.getDigestLength()]; 184 byte[] dig = new byte[hmac.getDigestLength()];
189
190 hmac.digest(dig); 185 hmac.digest(dig);
191
192 return dig; 186 return dig;
193 } 187 }
194 catch(DigestException e) { 188 catch (DigestException e) {
195 throw new IOException(e); 189 throw new IOException(e);
196 } 190 }
197 } 191 }
198 192
199 private boolean checkHashed(String entry, String hostname) { 193 private boolean checkHashed(String entry, String hostname) {
200 if(entry.startsWith("|1|") == false) { 194 if (entry.startsWith("|1|") == false) {
201 return false; 195 return false;
202 } 196 }
203 197
204 int delim_idx = entry.indexOf('|', 3); 198 int delim_idx = entry.indexOf('|', 3);
205 199
206 if(delim_idx == -1) { 200 if (delim_idx == -1) {
207 return false; 201 return false;
208 } 202 }
209 203
210 String salt_base64 = entry.substring(3, delim_idx); 204 String salt_base64 = entry.substring(3, delim_idx);
211 String hash_base64 = entry.substring(delim_idx + 1); 205 String hash_base64 = entry.substring(delim_idx + 1);
212
213 byte[] salt; 206 byte[] salt;
214 byte[] hash; 207 byte[] hash;
215 208
216 try { 209 try {
217 salt = Base64.decode(salt_base64.toCharArray()); 210 salt = Base64.decode(salt_base64.toCharArray());
218 hash = Base64.decode(hash_base64.toCharArray()); 211 hash = Base64.decode(hash_base64.toCharArray());
219 } 212 }
220 catch(IOException e) { 213 catch (IOException e) {
221 return false; 214 return false;
222 } 215 }
223 216
224 SHA1 sha1 = new SHA1(); 217 SHA1 sha1 = new SHA1();
225 218
226 if(salt.length != sha1.getDigestLength()) { 219 if (salt.length != sha1.getDigestLength()) {
227 return false; 220 return false;
228 } 221 }
229 222
230 byte[] dig = new byte[0]; 223 byte[] dig = new byte[0];
224
231 try { 225 try {
232 dig = hmacSha1Hash(salt, hostname); 226 dig = hmacSha1Hash(salt, hostname);
233 } 227 }
234 catch(IOException e) { 228 catch (IOException e) {
235 return false; 229 return false;
236 } 230 }
237 231
238 for(int i = 0; i < dig.length; i++) { 232 for (int i = 0; i < dig.length; i++) {
239 if(dig[i] != hash[i]) { 233 if (dig[i] != hash[i]) {
240 return false; 234 return false;
241 } 235 }
242 } 236 }
243 237
244 return true; 238 return true;
245 } 239 }
246 240
247 private int checkKey(String remoteHostname, PublicKey remoteKey) { 241 private int checkKey(String remoteHostname, PublicKey remoteKey) {
248 int result = HOSTKEY_IS_NEW; 242 int result = HOSTKEY_IS_NEW;
249 243
250 synchronized(publicKeys) { 244 synchronized (publicKeys) {
251 for(KnownHostsEntry ke : publicKeys) { 245 for (KnownHostsEntry ke : publicKeys) {
252 if(hostnameMatches(ke.patterns, remoteHostname) == false) { 246 if (hostnameMatches(ke.patterns, remoteHostname) == false) {
253 continue; 247 continue;
254 } 248 }
255 249
256 boolean res = matchKeys(ke.key, remoteKey); 250 boolean res = matchKeys(ke.key, remoteKey);
257 251
258 if(res == true) { 252 if (res == true) {
259 return HOSTKEY_IS_OK; 253 return HOSTKEY_IS_OK;
260 } 254 }
261 255
262 result = HOSTKEY_HAS_CHANGED; 256 result = HOSTKEY_HAS_CHANGED;
263 } 257 }
264 } 258 }
259
265 return result; 260 return result;
266 } 261 }
267 262
268 private List<Object> getAllKeys(String hostname) { 263 private List<Object> getAllKeys(String hostname) {
269 List<Object> keys = new ArrayList<Object>(); 264 List<Object> keys = new ArrayList<Object>();
270 265
271 synchronized(publicKeys) { 266 synchronized (publicKeys) {
272 for(KnownHostsEntry ke : publicKeys) { 267 for (KnownHostsEntry ke : publicKeys) {
273 if(hostnameMatches(ke.patterns, hostname) == false) { 268 if (hostnameMatches(ke.patterns, hostname) == false) {
274 continue; 269 continue;
275 } 270 }
276 271
277 keys.add(ke.key); 272 keys.add(ke.key);
278 } 273 }
294 * an array with hostkey algorithms is returned (i.e., an array of length 2). 289 * an array with hostkey algorithms is returned (i.e., an array of length 2).
295 */ 290 */
296 public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) { 291 public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) {
297 String[] algos = recommendHostkeyAlgorithms(hostname); 292 String[] algos = recommendHostkeyAlgorithms(hostname);
298 293
299 if(algos != null) { 294 if (algos != null) {
300 return algos; 295 return algos;
301 } 296 }
302 297
303 InetAddress[] ipAdresses; 298 InetAddress[] ipAdresses;
304 299
305 try { 300 try {
306 ipAdresses = InetAddress.getAllByName(hostname); 301 ipAdresses = InetAddress.getAllByName(hostname);
307 } 302 }
308 catch(UnknownHostException e) { 303 catch (UnknownHostException e) {
309 return null; 304 return null;
310 } 305 }
311 306
312 for(int i = 0; i < ipAdresses.length; i++) { 307 for (int i = 0; i < ipAdresses.length; i++) {
313 algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); 308 algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress());
314 309
315 if(algos != null) { 310 if (algos != null) {
316 return algos; 311 return algos;
317 } 312 }
318 } 313 }
319 314
320 return null; 315 return null;
321 } 316 }
322 317
323 private boolean hostnameMatches(String[] hostpatterns, String hostname) { 318 private boolean hostnameMatches(String[] hostpatterns, String hostname) {
324 boolean isMatch = false; 319 boolean isMatch = false;
325 boolean negate; 320 boolean negate;
326
327 hostname = hostname.toLowerCase(); 321 hostname = hostname.toLowerCase();
328 322
329 for(int k = 0; k < hostpatterns.length; k++) { 323 for (int k = 0; k < hostpatterns.length; k++) {
330 if(hostpatterns[k] == null) { 324 if (hostpatterns[k] == null) {
331 continue; 325 continue;
332 } 326 }
333 327
334 String pattern; 328 String pattern;
335 329
336 /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed 330 /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed
337 * entries in lines with multiple entries). 331 * entries in lines with multiple entries).
338 */ 332 */
339 333
340 if((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) { 334 if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) {
341 pattern = hostpatterns[k].substring(1); 335 pattern = hostpatterns[k].substring(1);
342 negate = true; 336 negate = true;
343 } 337 }
344 else { 338 else {
345 pattern = hostpatterns[k]; 339 pattern = hostpatterns[k];
346 negate = false; 340 negate = false;
347 } 341 }
348 342
349 /* Optimize, no need to check this entry */ 343 /* Optimize, no need to check this entry */
350 344
351 if((isMatch) && (negate == false)) { 345 if ((isMatch) && (negate == false)) {
352 continue; 346 continue;
353 } 347 }
354 348
355 /* Now compare */ 349 /* Now compare */
356 350
357 if(pattern.charAt(0) == '|') { 351 if (pattern.charAt(0) == '|') {
358 if(checkHashed(pattern, hostname)) { 352 if (checkHashed(pattern, hostname)) {
359 if(negate) { 353 if (negate) {
360 return false; 354 return false;
361 } 355 }
356
362 isMatch = true; 357 isMatch = true;
363 } 358 }
364 } 359 }
365 else { 360 else {
366 pattern = pattern.toLowerCase(); 361 pattern = pattern.toLowerCase();
367 362
368 if((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) { 363 if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) {
369 if(pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) { 364 if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) {
370 if(negate) { 365 if (negate) {
371 return false; 366 return false;
372 } 367 }
368
373 isMatch = true; 369 isMatch = true;
374 } 370 }
375 } 371 }
376 else if(pattern.compareTo(hostname) == 0) { 372 else if (pattern.compareTo(hostname) == 0) {
377 if(negate) { 373 if (negate) {
378 return false; 374 return false;
379 } 375 }
376
380 isMatch = true; 377 isMatch = true;
381 } 378 }
382 } 379 }
383 } 380 }
384 381
386 } 383 }
387 384
388 private void initialize(char[] knownHostsData) throws IOException { 385 private void initialize(char[] knownHostsData) throws IOException {
389 BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData)); 386 BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData));
390 387
391 while(true) { 388 while (true) {
392 String line = br.readLine(); 389 String line = br.readLine();
393 390
394 if(line == null) { 391 if (line == null) {
395 break; 392 break;
396 } 393 }
397 394
398 line = line.trim(); 395 line = line.trim();
399 396
400 if(line.startsWith("#")) { 397 if (line.startsWith("#")) {
401 continue; 398 continue;
402 } 399 }
403 400
404 String[] arr = line.split(" "); 401 String[] arr = line.split(" ");
405 402
406 if(arr.length >= 3) { 403 if (arr.length >= 3) {
407 if ((arr[1].compareTo("ssh-rsa") == 0) || 404 if ((arr[1].compareTo("ssh-rsa") == 0) ||
408 (arr[1].compareTo("ssh-dss") == 0) || 405 (arr[1].compareTo("ssh-dss") == 0) ||
409 (arr[1].startsWith("ecdsa-sha2-"))) { 406 (arr[1].startsWith("ecdsa-sha2-"))) {
410 String[] hostnames = arr[0].split(","); 407 String[] hostnames = arr[0].split(",");
411
412 byte[] msg = Base64.decode(arr[2].toCharArray()); 408 byte[] msg = Base64.decode(arr[2].toCharArray());
413 409
414 try { 410 try {
415 addHostkey(hostnames, arr[1], msg); 411 addHostkey(hostnames, arr[1], msg);
416 } 412 }
417 catch(IOException e) { 413 catch (IOException e) {
418 continue; 414 continue;
419 } 415 }
420 } 416 }
421 } 417 }
422 } 418 }
423 } 419 }
424 420
425 private void initialize(File knownHosts) throws IOException { 421 private void initialize(File knownHosts) throws IOException {
426 char[] buff = new char[512]; 422 char[] buff = new char[512];
427
428 CharArrayWriter cw = new CharArrayWriter(); 423 CharArrayWriter cw = new CharArrayWriter();
429
430 knownHosts.createNewFile(); 424 knownHosts.createNewFile();
431
432 FileReader fr = new FileReader(knownHosts); 425 FileReader fr = new FileReader(knownHosts);
433 426
434 while(true) { 427 while (true) {
435 int len = fr.read(buff); 428 int len = fr.read(buff);
436 if(len < 0) { 429
430 if (len < 0) {
437 break; 431 break;
438 } 432 }
433
439 cw.write(buff, 0, len); 434 cw.write(buff, 0, len);
440 } 435 }
441 436
442 fr.close(); 437 fr.close();
443
444 initialize(cw.toCharArray()); 438 initialize(cw.toCharArray());
445 } 439 }
446 440
447 private final boolean matchKeys(PublicKey key1, PublicKey key2) { 441 private final boolean matchKeys(PublicKey key1, PublicKey key2) {
448 return key1.equals(key2); 442 return key1.equals(key2);
449 } 443 }
450 444
451 private boolean pseudoRegex(char[] pattern, int i, char[] match, int j) { 445 private boolean pseudoRegex(char[] pattern, int i, char[] match, int j) {
452 /* This matching logic is equivalent to the one present in OpenSSH 4.1 */ 446 /* This matching logic is equivalent to the one present in OpenSSH 4.1 */
453 447 while (true) {
454 while(true) { 448 /* Are we at the end of the pattern? */
455 /* Are we at the end of the pattern? */ 449 if (pattern.length == i) {
456
457 if(pattern.length == i) {
458 return (match.length == j); 450 return (match.length == j);
459 } 451 }
460 452
461 if(pattern[i] == '*') { 453 if (pattern[i] == '*') {
462 i++; 454 i++;
463 455
464 if(pattern.length == i) { 456 if (pattern.length == i) {
465 return true; 457 return true;
466 } 458 }
467 459
468 if((pattern[i] != '*') && (pattern[i] != '?')) { 460 if ((pattern[i] != '*') && (pattern[i] != '?')) {
469 while(true) { 461 while (true) {
470 if((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) { 462 if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) {
471 return true; 463 return true;
472 } 464 }
465
473 j++; 466 j++;
474 if(match.length == j) { 467
468 if (match.length == j) {
475 return false; 469 return false;
476 } 470 }
477 } 471 }
478 } 472 }
479 473
480 while(true) { 474 while (true) {
481 if(pseudoRegex(pattern, i, match, j)) { 475 if (pseudoRegex(pattern, i, match, j)) {
482 return true; 476 return true;
483 } 477 }
478
484 j++; 479 j++;
485 if(match.length == j) { 480
481 if (match.length == j) {
486 return false; 482 return false;
487 } 483 }
488 } 484 }
489 } 485 }
490 486
491 if(match.length == j) { 487 if (match.length == j) {
492 return false; 488 return false;
493 } 489 }
494 490
495 if((pattern[i] != '?') && (pattern[i] != match[j])) { 491 if ((pattern[i] != '?') && (pattern[i] != match[j])) {
496 return false; 492 return false;
497 } 493 }
498 494
499 i++; 495 i++;
500 j++; 496 j++;
501 } 497 }
502 } 498 }
503 499
504 private String[] recommendHostkeyAlgorithms(String hostname) { 500 private String[] recommendHostkeyAlgorithms(String hostname) {
505 String preferredAlgo = null; 501 String preferredAlgo = null;
506
507 List<Object> keys = getAllKeys(hostname); 502 List<Object> keys = getAllKeys(hostname);
508 503
509 for(Object key : keys) { 504 for (Object key : keys) {
510 String thisAlgo; 505 String thisAlgo;
511 506
512 if(key instanceof RSAPublicKey) { 507 if (key instanceof RSAPublicKey) {
513 thisAlgo = "ssh-rsa"; 508 thisAlgo = "ssh-rsa";
514 } 509 }
515 else if(key instanceof DSAPublicKey) { 510 else if (key instanceof DSAPublicKey) {
516 thisAlgo = "ssh-dss"; 511 thisAlgo = "ssh-dss";
517 } 512 }
518 else if (key instanceof ECPublicKey) { 513 else if (key instanceof ECPublicKey) {
519 ECPublicKey ecPub = (ECPublicKey) key; 514 ECPublicKey ecPub = (ECPublicKey) key;
520 String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize()); 515 String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize());
522 } 517 }
523 else { 518 else {
524 continue; 519 continue;
525 } 520 }
526 521
527 if(preferredAlgo != null) { 522 if (preferredAlgo != null) {
528 /* If we find different key types, then return null */ 523 /* If we find different key types, then return null */
529 524 if (preferredAlgo.compareTo(thisAlgo) != 0) {
530 if(preferredAlgo.compareTo(thisAlgo) != 0) {
531 return null; 525 return null;
532 } 526 }
533 } 527 }
534 else { 528 else {
535 preferredAlgo = thisAlgo; 529 preferredAlgo = thisAlgo;
536 } 530 }
537 } 531 }
538 532
539 /* If we did not find anything that we know of, return null */ 533 /* If we did not find anything that we know of, return null */
540 534
541 if(preferredAlgo == null) { 535 if (preferredAlgo == null) {
542 return null; 536 return null;
543 } 537 }
544 538
545 /* Now put the preferred algo to the start of the array. 539 /* Now put the preferred algo to the start of the array.
546 * You may ask yourself why we do it that way - basically, we could just 540 * You may ask yourself why we do it that way - basically, we could just
547 * return only the preferred algorithm: since we have a saved key of that 541 * return only the preferred algorithm: since we have a saved key of that
548 * type (sent earlier from the remote host), then that should work out. 542 * type (sent earlier from the remote host), then that should work out.
549 * However, imagine that the server is (for whatever reasons) not offering 543 * However, imagine that the server is (for whatever reasons) not offering
550 * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and 544 * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and
551 * now "ssh-dss" is being used). If we then do not let the server send us 545 * now "ssh-dss" is being used). If we then do not let the server send us
552 * a fresh key of the new type, then we shoot ourself into the foot: 546 * a fresh key of the new type, then we shoot ourself into the foot:
553 * the connection cannot be established and hence the user cannot decide 547 * the connection cannot be established and hence the user cannot decide
554 * if he/she wants to accept the new key. 548 * if he/she wants to accept the new key.
555 */ 549 */
556 550
557 if(preferredAlgo.equals("ssh-rsa")) { 551 if (preferredAlgo.equals("ssh-rsa")) {
558 return new String[]{"ssh-rsa", "ssh-dss", "ecdsa-sha2-nistp256"}; 552 return new String[] {"ssh-rsa", "ssh-dss", "ecdsa-sha2-nistp256"};
559 } 553 }
560 554
561 return new String[]{"ssh-dss", "ssh-rsa", "ecdsa-sha2-nistp256"}; 555 return new String[] {"ssh-dss", "ssh-rsa", "ecdsa-sha2-nistp256"};
562 } 556 }
563 557
564 /** 558 /**
565 * Checks the internal hostkey database for the given hostkey. 559 * Checks the internal hostkey database for the given hostkey.
566 * If no matching key can be found, then the hostname is resolved to an IP address 560 * If no matching key can be found, then the hostname is resolved to an IP address
578 * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. 572 * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type.
579 */ 573 */
580 public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { 574 public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException {
581 PublicKey remoteKey; 575 PublicKey remoteKey;
582 576
583 if("ssh-rsa".equals(serverHostKeyAlgorithm)) { 577 if ("ssh-rsa".equals(serverHostKeyAlgorithm)) {
584 remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); 578 remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey);
585 } 579 }
586 else if("ssh-dss".equals(serverHostKeyAlgorithm)) { 580 else if ("ssh-dss".equals(serverHostKeyAlgorithm)) {
587 remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); 581 remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey);
588 } 582 }
589 else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) { 583 else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) {
590 remoteKey = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); 584 remoteKey = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey);
591 } 585 }
593 throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); 587 throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm);
594 } 588 }
595 589
596 int result = checkKey(hostname, remoteKey); 590 int result = checkKey(hostname, remoteKey);
597 591
598 if(result == HOSTKEY_IS_OK) { 592 if (result == HOSTKEY_IS_OK) {
599 return result; 593 return result;
600 } 594 }
601 595
602 InetAddress[] ipAdresses; 596 InetAddress[] ipAdresses;
603 597
604 try { 598 try {
605 ipAdresses = InetAddress.getAllByName(hostname); 599 ipAdresses = InetAddress.getAllByName(hostname);
606 } 600 }
607 catch(UnknownHostException e) { 601 catch (UnknownHostException e) {
608 return result; 602 return result;
609 } 603 }
610 604
611 for(int i = 0; i < ipAdresses.length; i++) { 605 for (int i = 0; i < ipAdresses.length; i++) {
612 int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); 606 int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey);
613 607
614 if(newresult == HOSTKEY_IS_OK) { 608 if (newresult == HOSTKEY_IS_OK) {
615 return newresult; 609 return newresult;
616 } 610 }
617 611
618 if(newresult == HOSTKEY_HAS_CHANGED) { 612 if (newresult == HOSTKEY_HAS_CHANGED) {
619 result = HOSTKEY_HAS_CHANGED; 613 result = HOSTKEY_HAS_CHANGED;
620 } 614 }
621 } 615 }
622 616
623 return result; 617 return result;
634 * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. 628 * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
635 * @throws IOException 629 * @throws IOException
636 */ 630 */
637 public static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm, 631 public static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm,
638 byte[] serverHostKey) throws IOException { 632 byte[] serverHostKey) throws IOException {
639 if((hostnames == null) || (hostnames.length == 0)) { 633 if ((hostnames == null) || (hostnames.length == 0)) {
640 throw new IllegalArgumentException("Need at least one hostname specification"); 634 throw new IllegalArgumentException("Need at least one hostname specification");
641 } 635 }
642 636
643 if((serverHostKeyAlgorithm == null) || (serverHostKey == null)) { 637 if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) {
644 throw new IllegalArgumentException(); 638 throw new IllegalArgumentException();
645 } 639 }
646 640
647 CharArrayWriter writer = new CharArrayWriter(); 641 CharArrayWriter writer = new CharArrayWriter();
648 642
649 for(int i = 0; i < hostnames.length; i++) { 643 for (int i = 0; i < hostnames.length; i++) {
650 if(i != 0) { 644 if (i != 0) {
651 writer.write(','); 645 writer.write(',');
652 } 646 }
647
653 writer.write(hostnames[i]); 648 writer.write(hostnames[i]);
654 } 649 }
655 650
656 writer.write(' '); 651 writer.write(' ');
657 writer.write(serverHostKeyAlgorithm); 652 writer.write(serverHostKeyAlgorithm);
658 writer.write(' '); 653 writer.write(' ');
659 writer.write(Base64.encode(serverHostKey)); 654 writer.write(Base64.encode(serverHostKey));
660 writer.write("\n"); 655 writer.write("\n");
661
662 char[] entry = writer.toCharArray(); 656 char[] entry = writer.toCharArray();
663
664 RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw"); 657 RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw");
665
666 long len = raf.length(); 658 long len = raf.length();
667 659
668 if(len > 0) { 660 if (len > 0) {
669 raf.seek(len - 1); 661 raf.seek(len - 1);
670 int last = raf.read(); 662 int last = raf.read();
671 if(last != '\n') { 663
664 if (last != '\n') {
672 raf.write('\n'); 665 raf.write('\n');
673 } 666 }
674 } 667 }
675 668
676 raf.write(StringEncoder.GetBytes(new String(entry))); 669 raf.write(StringEncoder.GetBytes(new String(entry)));
686 * @return the raw fingerprint 679 * @return the raw fingerprint
687 */ 680 */
688 static private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) throws IOException { 681 static private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) throws IOException {
689 Digest dig; 682 Digest dig;
690 683
691 if("md5".equals(type)) { 684 if ("md5".equals(type)) {
692 dig = new MD5(); 685 dig = new MD5();
693 } 686 }
694 else if("sha1".equals(type)) { 687 else if ("sha1".equals(type)) {
695 dig = new SHA1(); 688 dig = new SHA1();
696 } 689 }
697 else { 690 else {
698 throw new IllegalArgumentException("Unknown hash type " + type); 691 throw new IllegalArgumentException("Unknown hash type " + type);
699 } 692 }
700 693
701 if("ssh-rsa".equals(keyType)) { 694 if ("ssh-rsa".equals(keyType)) {
702 } 695 }
703 else if("ssh-dss".equals(keyType)) { 696 else if ("ssh-dss".equals(keyType)) {
704 } 697 }
705 else if (keyType.startsWith("ecdsa-sha2-")) { 698 else if (keyType.startsWith("ecdsa-sha2-")) {
706 } 699 }
707 else { 700 else {
708 throw new IllegalArgumentException("Unknown key type " + keyType); 701 throw new IllegalArgumentException("Unknown key type " + keyType);
709 } 702 }
710 703
711 if(hostkey == null) { 704 if (hostkey == null) {
712 throw new IllegalArgumentException("hostkey is null"); 705 throw new IllegalArgumentException("hostkey is null");
713 } 706 }
714 707
715 dig.update(hostkey); 708 dig.update(hostkey);
716 byte[] res = new byte[dig.getDigestLength()]; 709 byte[] res = new byte[dig.getDigestLength()];
710
717 try { 711 try {
718 dig.digest(res); 712 dig.digest(res);
719 } 713 }
720 catch(DigestException e) { 714 catch (DigestException e) {
721 throw new IOException(e); 715 throw new IOException(e);
722 } 716 }
717
723 return res; 718 return res;
724 } 719 }
725 720
726 /** 721 /**
727 * Convert a raw fingerprint to hex representation (XX:YY:ZZ...). 722 * Convert a raw fingerprint to hex representation (XX:YY:ZZ...).
729 * @param fingerprint raw fingerprint 724 * @param fingerprint raw fingerprint
730 * @return the hex representation 725 * @return the hex representation
731 */ 726 */
732 static private String rawToHexFingerprint(byte[] fingerprint) { 727 static private String rawToHexFingerprint(byte[] fingerprint) {
733 final char[] alpha = "0123456789abcdef".toCharArray(); 728 final char[] alpha = "0123456789abcdef".toCharArray();
734
735 StringBuilder sb = new StringBuilder(); 729 StringBuilder sb = new StringBuilder();
736 730
737 for(int i = 0; i < fingerprint.length; i++) { 731 for (int i = 0; i < fingerprint.length; i++) {
738 if(i != 0) { 732 if (i != 0) {
739 sb.append(':'); 733 sb.append(':');
740 } 734 }
735
741 int b = fingerprint[i] & 0xff; 736 int b = fingerprint[i] & 0xff;
742 sb.append(alpha[b >> 4]); 737 sb.append(alpha[b >> 4]);
743 sb.append(alpha[b & 15]); 738 sb.append(alpha[b & 15]);
744 } 739 }
745 740
753 * @return the bubblebabble representation 748 * @return the bubblebabble representation
754 */ 749 */
755 static private String rawToBubblebabbleFingerprint(byte[] raw) { 750 static private String rawToBubblebabbleFingerprint(byte[] raw) {
756 final char[] v = "aeiouy".toCharArray(); 751 final char[] v = "aeiouy".toCharArray();
757 final char[] c = "bcdfghklmnprstvzx".toCharArray(); 752 final char[] c = "bcdfghklmnprstvzx".toCharArray();
758
759 StringBuilder sb = new StringBuilder(); 753 StringBuilder sb = new StringBuilder();
760
761 int seed = 1; 754 int seed = 1;
762
763 int rounds = (raw.length / 2) + 1; 755 int rounds = (raw.length / 2) + 1;
764
765 sb.append('x'); 756 sb.append('x');
766 757
767 for(int i = 0; i < rounds; i++) { 758 for (int i = 0; i < rounds; i++) {
768 if(((i + 1) < rounds) || ((raw.length) % 2 != 0)) { 759 if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) {
769 sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); 760 sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]);
770 sb.append(c[(raw[2 * i] >> 2) & 15]); 761 sb.append(c[(raw[2 * i] >> 2) & 15]);
771 sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); 762 sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]);
772 763
773 if((i + 1) < rounds) { 764 if ((i + 1) < rounds) {
774 sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); 765 sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]);
775 sb.append('-'); 766 sb.append('-');
776 sb.append(c[(((raw[(2 * i) + 1]))) & 15]); 767 sb.append(c[(((raw[(2 * i) + 1]))) & 15]);
777 // As long as seed >= 0, seed will be >= 0 afterwards 768 // As long as seed >= 0, seed will be >= 0 afterwards
778 seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; 769 seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36;
784 sb.append(v[seed / 6]); 775 sb.append(v[seed / 6]);
785 } 776 }
786 } 777 }
787 778
788 sb.append('x'); 779 sb.append('x');
789
790 return sb.toString(); 780 return sb.toString();
791 } 781 }
792 782
793 /** 783 /**
794 * Convert a ssh2 key-blob into a human readable hex fingerprint. 784 * Convert a ssh2 key-blob into a human readable hex fingerprint.