001: /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
002: /*
003: Copyright (c) 2002-2008 ymnk, JCraft,Inc. All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without
006: modification, are permitted provided that the following conditions are met:
007:
008: 1. Redistributions of source code must retain the above copyright notice,
009: this list of conditions and the following disclaimer.
010:
011: 2. Redistributions in binary form must reproduce the above copyright
012: notice, this list of conditions and the following disclaimer in
013: the documentation and/or other materials provided with the distribution.
014:
015: 3. The names of the authors may not be used to endorse or promote products
016: derived from this software without specific prior written permission.
017:
018: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
019: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
020: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
021: INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
022: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
024: OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
027: EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: */
029:
030: package com.jcraft.jsch;
031:
032: import java.io.*;
033:
034: public class KnownHosts implements HostKeyRepository {
035: private static final String _known_hosts = "known_hosts";
036:
037: /*
038: static final int SSHDSS=0;
039: static final int SSHRSA=1;
040: static final int UNKNOWN=2;
041: */
042:
043: private JSch jsch = null;
044: private String known_hosts = null;
045: private java.util.Vector pool = null;
046:
047: private MAC hmacsha1 = null;
048:
049: KnownHosts(JSch jsch) {
050: super ();
051: this .jsch = jsch;
052: pool = new java.util.Vector();
053: }
054:
055: void setKnownHosts(String foo) throws JSchException {
056: try {
057: known_hosts = foo;
058: FileInputStream fis = new FileInputStream(foo);
059: setKnownHosts(fis);
060: } catch (FileNotFoundException e) {
061: }
062: }
063:
064: void setKnownHosts(InputStream foo) throws JSchException {
065: pool.removeAllElements();
066: StringBuffer sb = new StringBuffer();
067: byte i;
068: int j;
069: boolean error = false;
070: try {
071: InputStream fis = foo;
072: String host;
073: String key = null;
074: int type;
075: byte[] buf = new byte[1024];
076: int bufl = 0;
077: loop: while (true) {
078: bufl = 0;
079: while (true) {
080: j = fis.read();
081: if (j == -1) {
082: if (bufl == 0) {
083: break loop;
084: } else {
085: break;
086: }
087: }
088: if (j == 0x0d) {
089: continue;
090: }
091: if (j == 0x0a) {
092: break;
093: }
094: if (buf.length <= bufl) {
095: if (bufl > 1024 * 10)
096: break; // too long...
097: byte[] newbuf = new byte[buf.length * 2];
098: System.arraycopy(buf, 0, newbuf, 0, buf.length);
099: buf = newbuf;
100: }
101: buf[bufl++] = (byte) j;
102: }
103:
104: j = 0;
105: while (j < bufl) {
106: i = buf[j];
107: if (i == ' ' || i == '\t') {
108: j++;
109: continue;
110: }
111: if (i == '#') {
112: addInvalidLine(new String(buf, 0, bufl));
113: continue loop;
114: }
115: break;
116: }
117: if (j >= bufl) {
118: addInvalidLine(new String(buf, 0, bufl));
119: continue loop;
120: }
121:
122: sb.setLength(0);
123: while (j < bufl) {
124: i = buf[j++];
125: if (i == 0x20 || i == '\t') {
126: break;
127: }
128: sb.append((char) i);
129: }
130: host = sb.toString();
131: if (j >= bufl || host.length() == 0) {
132: addInvalidLine(new String(buf, 0, bufl));
133: continue loop;
134: }
135:
136: sb.setLength(0);
137: type = -1;
138: while (j < bufl) {
139: i = buf[j++];
140: if (i == 0x20 || i == '\t') {
141: break;
142: }
143: sb.append((char) i);
144: }
145: if (sb.toString().equals("ssh-dss")) {
146: type = HostKey.SSHDSS;
147: } else if (sb.toString().equals("ssh-rsa")) {
148: type = HostKey.SSHRSA;
149: } else {
150: j = bufl;
151: }
152: if (j >= bufl) {
153: addInvalidLine(new String(buf, 0, bufl));
154: continue loop;
155: }
156:
157: sb.setLength(0);
158: while (j < bufl) {
159: i = buf[j++];
160: if (i == 0x0d) {
161: continue;
162: }
163: if (i == 0x0a) {
164: break;
165: }
166: sb.append((char) i);
167: }
168: key = sb.toString();
169: if (key.length() == 0) {
170: addInvalidLine(new String(buf, 0, bufl));
171: continue loop;
172: }
173:
174: //System.err.println(host);
175: //System.err.println("|"+key+"|");
176:
177: HostKey hk = null;
178: hk = new HashedHostKey(host, type, Util.fromBase64(key
179: .getBytes(), 0, key.length()));
180: pool.addElement(hk);
181: }
182: fis.close();
183: if (error) {
184: throw new JSchException("KnownHosts: invalid format");
185: }
186: } catch (Exception e) {
187: if (e instanceof JSchException)
188: throw (JSchException) e;
189: if (e instanceof Throwable)
190: throw new JSchException(e.toString(), (Throwable) e);
191: throw new JSchException(e.toString());
192: }
193: }
194:
195: private void addInvalidLine(String line) throws JSchException {
196: HostKey hk = new HostKey(line, HostKey.UNKNOWN, null);
197: pool.addElement(hk);
198: }
199:
200: String getKnownHostsFile() {
201: return known_hosts;
202: }
203:
204: public String getKnownHostsRepositoryID() {
205: return known_hosts;
206: }
207:
208: public int check(String host, byte[] key) {
209: int result = NOT_INCLUDED;
210: if (host == null) {
211: return result;
212: }
213:
214: int type = getType(key);
215: HostKey hk;
216:
217: synchronized (pool) {
218: for (int i = 0; i < pool.size(); i++) {
219: hk = (HostKey) (pool.elementAt(i));
220: if (hk.isMatched(host) && hk.type == type) {
221: if (Util.array_equals(hk.key, key)) {
222: //System.err.println("find!!");
223: return OK;
224: } else {
225: result = CHANGED;
226: }
227: }
228: }
229: }
230: //System.err.println("fail!!");
231: return result;
232: }
233:
234: public void add(HostKey hostkey, UserInfo userinfo) {
235: int type = hostkey.type;
236: String host = hostkey.getHost();
237: byte[] key = hostkey.key;
238:
239: HostKey hk = null;
240: synchronized (pool) {
241: for (int i = 0; i < pool.size(); i++) {
242: hk = (HostKey) (pool.elementAt(i));
243: if (hk.isMatched(host) && hk.type == type) {
244: /*
245: if(Util.array_equals(hk.key, key)){ return; }
246: if(hk.host.equals(host)){
247: hk.key=key;
248: return;
249: }
250: else{
251: hk.host=deleteSubString(hk.host, host);
252: break;
253: }
254: */
255: }
256: }
257: }
258:
259: hk = hostkey;
260:
261: pool.addElement(hk);
262:
263: String bar = getKnownHostsRepositoryID();
264: if (bar != null) {
265: boolean foo = true;
266: File goo = new File(bar);
267: if (!goo.exists()) {
268: foo = false;
269: if (userinfo != null) {
270: foo = userinfo.promptYesNo(bar
271: + " does not exist.\n"
272: + "Are you sure you want to create it?");
273: goo = goo.getParentFile();
274: if (foo && goo != null && !goo.exists()) {
275: foo = userinfo
276: .promptYesNo("The parent directory "
277: + goo
278: + " does not exist.\n"
279: + "Are you sure you want to create it?");
280: if (foo) {
281: if (!goo.mkdirs()) {
282: userinfo.showMessage(goo
283: + " has not been created.");
284: foo = false;
285: } else {
286: userinfo
287: .showMessage(goo
288: + " has been succesfully created.\nPlease check its access permission.");
289: }
290: }
291: }
292: if (goo == null)
293: foo = false;
294: }
295: }
296: if (foo) {
297: try {
298: sync(bar);
299: } catch (Exception e) {
300: System.err.println("sync known_hosts: " + e);
301: }
302: }
303: }
304: }
305:
306: public HostKey[] getHostKey() {
307: return getHostKey(null, null);
308: }
309:
310: public HostKey[] getHostKey(String host, String type) {
311: synchronized (pool) {
312: int count = 0;
313: for (int i = 0; i < pool.size(); i++) {
314: HostKey hk = (HostKey) pool.elementAt(i);
315: if (hk.type == HostKey.UNKNOWN)
316: continue;
317: if (host == null
318: || (hk.isMatched(host) && (type == null || hk
319: .getType().equals(type)))) {
320: count++;
321: }
322: }
323: if (count == 0)
324: return null;
325: HostKey[] foo = new HostKey[count];
326: int j = 0;
327: for (int i = 0; i < pool.size(); i++) {
328: HostKey hk = (HostKey) pool.elementAt(i);
329: if (hk.type == HostKey.UNKNOWN)
330: continue;
331: if (host == null
332: || (hk.isMatched(host) && (type == null || hk
333: .getType().equals(type)))) {
334: foo[j++] = hk;
335: }
336: }
337: return foo;
338: }
339: }
340:
341: public void remove(String host, String type) {
342: remove(host, type, null);
343: }
344:
345: public void remove(String host, String type, byte[] key) {
346: boolean sync = false;
347: synchronized (pool) {
348: for (int i = 0; i < pool.size(); i++) {
349: HostKey hk = (HostKey) (pool.elementAt(i));
350: if (host == null
351: || (hk.isMatched(host) && (type == null || (hk
352: .getType().equals(type) && (key == null || Util
353: .array_equals(key, hk.key)))))) {
354: String hosts = hk.getHost();
355: if (hosts.equals(host)
356: || ((hk instanceof HashedHostKey) && ((HashedHostKey) hk)
357: .isHashed())) {
358: pool.removeElement(hk);
359: } else {
360: hk.host = deleteSubString(hosts, host);
361: }
362: sync = true;
363: }
364: }
365: }
366: if (sync) {
367: try {
368: sync();
369: } catch (Exception e) {
370: }
371: ;
372: }
373: }
374:
375: protected void sync() throws IOException {
376: if (known_hosts != null)
377: sync(known_hosts);
378: }
379:
380: protected synchronized void sync(String foo) throws IOException {
381: if (foo == null)
382: return;
383: FileOutputStream fos = new FileOutputStream(foo);
384: dump(fos);
385: fos.close();
386: }
387:
388: private static final byte[] space = { (byte) 0x20 };
389: private static final byte[] cr = "\n".getBytes();
390:
391: void dump(OutputStream out) throws IOException {
392: try {
393: HostKey hk;
394: synchronized (pool) {
395: for (int i = 0; i < pool.size(); i++) {
396: hk = (HostKey) (pool.elementAt(i));
397: //hk.dump(out);
398: String host = hk.getHost();
399: String type = hk.getType();
400: if (type.equals("UNKNOWN")) {
401: out.write(host.getBytes());
402: out.write(cr);
403: continue;
404: }
405: out.write(host.getBytes());
406: out.write(space);
407: out.write(type.getBytes());
408: out.write(space);
409: out.write(hk.getKey().getBytes());
410: out.write(cr);
411: }
412: }
413: } catch (Exception e) {
414: System.err.println(e);
415: }
416: }
417:
418: private int getType(byte[] key) {
419: if (key[8] == 'd')
420: return HostKey.SSHDSS;
421: if (key[8] == 'r')
422: return HostKey.SSHRSA;
423: return HostKey.UNKNOWN;
424: }
425:
426: private String deleteSubString(String hosts, String host) {
427: int i = 0;
428: int hostlen = host.length();
429: int hostslen = hosts.length();
430: int j;
431: while (i < hostslen) {
432: j = hosts.indexOf(',', i);
433: if (j == -1)
434: break;
435: if (!host.equals(hosts.substring(i, j))) {
436: i = j + 1;
437: continue;
438: }
439: return hosts.substring(0, i) + hosts.substring(j + 1);
440: }
441: if (hosts.endsWith(host) && hostslen - i == hostlen) {
442: return hosts.substring(0, (hostlen == hostslen) ? 0
443: : hostslen - hostlen - 1);
444: }
445: return hosts;
446: }
447:
448: private synchronized MAC getHMACSHA1() {
449: if (hmacsha1 == null) {
450: try {
451: Class c = Class.forName(jsch.getConfig("hmac-sha1"));
452: hmacsha1 = (MAC) (c.newInstance());
453: } catch (Exception e) {
454: System.err.println("hmacsha1: " + e);
455: }
456: }
457: return hmacsha1;
458: }
459:
460: HostKey createHashedHostKey(String host, byte[] key)
461: throws JSchException {
462: HashedHostKey hhk = new HashedHostKey(host, key);
463: hhk.hash();
464: return hhk;
465: }
466:
467: class HashedHostKey extends HostKey {
468: private static final String HASH_MAGIC = "|1|";
469: private static final String HASH_DELIM = "|";
470:
471: private boolean hashed = false;
472: byte[] salt = null;
473: byte[] hash = null;
474:
475: HashedHostKey(String host, byte[] key) throws JSchException {
476: this (host, GUESS, key);
477: }
478:
479: HashedHostKey(String host, int type, byte[] key)
480: throws JSchException {
481: super (host, type, key);
482: if (this .host.startsWith(HASH_MAGIC)
483: && this .host.substring(HASH_MAGIC.length())
484: .indexOf(HASH_DELIM) > 0) {
485: String data = this .host.substring(HASH_MAGIC.length());
486: String _salt = data.substring(0, data
487: .indexOf(HASH_DELIM));
488: String _hash = data
489: .substring(data.indexOf(HASH_DELIM) + 1);
490: salt = Util.fromBase64(_salt.getBytes(), 0, _salt
491: .length());
492: hash = Util.fromBase64(_hash.getBytes(), 0, _hash
493: .length());
494: if (salt.length != 20 || // block size of hmac-sha1
495: hash.length != 20) {
496: salt = null;
497: hash = null;
498: return;
499: }
500: hashed = true;
501: }
502: }
503:
504: boolean isMatched(String _host) {
505: if (!hashed) {
506: return super .isMatched(_host);
507: }
508: MAC macsha1 = getHMACSHA1();
509: try {
510: synchronized (macsha1) {
511: macsha1.init(salt);
512: byte[] foo = _host.getBytes();
513: macsha1.update(foo, 0, foo.length);
514: byte[] bar = new byte[macsha1.getBlockSize()];
515: macsha1.doFinal(bar, 0);
516: return Util.array_equals(hash, bar);
517: }
518: } catch (Exception e) {
519: System.out.println(e);
520: }
521: return false;
522: }
523:
524: boolean isHashed() {
525: return hashed;
526: }
527:
528: void hash() {
529: if (hashed)
530: return;
531: MAC macsha1 = getHMACSHA1();
532: if (salt == null) {
533: Random random = Session.random;
534: synchronized (random) {
535: salt = new byte[macsha1.getBlockSize()];
536: random.fill(salt, 0, salt.length);
537: }
538: }
539: try {
540: synchronized (macsha1) {
541: macsha1.init(salt);
542: byte[] foo = host.getBytes();
543: macsha1.update(foo, 0, foo.length);
544: hash = new byte[macsha1.getBlockSize()];
545: macsha1.doFinal(hash, 0);
546: }
547: } catch (Exception e) {
548: }
549: host = HASH_MAGIC
550: + new String(Util.toBase64(salt, 0, salt.length))
551: + HASH_DELIM
552: + new String(Util.toBase64(hash, 0, hash.length));
553: hashed = true;
554: }
555: }
556: }
|