]>
Commit | Line | Data |
---|---|---|
0763e16d JW |
1 | /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
2 | /* | |
3 | Copyright (c) 2002-2010 ymnk, JCraft,Inc. All rights reserved. | |
4 | ||
5 | Redistribution and use in source and binary forms, with or without | |
6 | modification, are permitted provided that the following conditions are met: | |
7 | ||
8 | 1. Redistributions of source code must retain the above copyright notice, | |
9 | this list of conditions and the following disclaimer. | |
10 | ||
11 | 2. Redistributions in binary form must reproduce the above copyright | |
12 | notice, this list of conditions and the following disclaimer in | |
13 | the documentation and/or other materials provided with the distribution. | |
14 | ||
15 | 3. The names of the authors may not be used to endorse or promote products | |
16 | derived from this software without specific prior written permission. | |
17 | ||
18 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, | |
19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
20 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, | |
21 | INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, | |
22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, | |
24 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
27 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | */ | |
29 | ||
30 | package com.jcraft.jsch; | |
31 | ||
32 | import java.io.*; | |
33 | ||
34 | public | |
35 | class KnownHosts implements HostKeyRepository{ | |
36 | private static final String _known_hosts="known_hosts"; | |
37 | ||
38 | /* | |
39 | static final int SSHDSS=0; | |
40 | static final int SSHRSA=1; | |
41 | static final int UNKNOWN=2; | |
42 | */ | |
43 | ||
44 | private JSch jsch=null; | |
45 | private String known_hosts=null; | |
46 | private java.util.Vector pool=null; | |
47 | ||
48 | private MAC hmacsha1=null; | |
49 | ||
50 | KnownHosts(JSch jsch){ | |
51 | super(); | |
52 | this.jsch=jsch; | |
53 | pool=new java.util.Vector(); | |
54 | } | |
55 | ||
56 | void setKnownHosts(String foo) throws JSchException{ | |
57 | try{ | |
58 | known_hosts=foo; | |
59 | FileInputStream fis=new FileInputStream(foo); | |
60 | setKnownHosts(fis); | |
61 | } | |
62 | catch(FileNotFoundException e){ | |
63 | } | |
64 | } | |
65 | void setKnownHosts(InputStream foo) throws JSchException{ | |
66 | pool.removeAllElements(); | |
67 | StringBuffer sb=new StringBuffer(); | |
68 | byte i; | |
69 | int j; | |
70 | boolean error=false; | |
71 | try{ | |
72 | InputStream fis=foo; | |
73 | String host; | |
74 | String key=null; | |
75 | int type; | |
76 | byte[] buf=new byte[1024]; | |
77 | int bufl=0; | |
78 | loop: | |
79 | while(true){ | |
80 | bufl=0; | |
81 | while(true){ | |
82 | j=fis.read(); | |
83 | if(j==-1){ | |
84 | if(bufl==0){ break loop; } | |
85 | else{ break; } | |
86 | } | |
87 | if(j==0x0d){ continue; } | |
88 | if(j==0x0a){ break; } | |
89 | if(buf.length<=bufl){ | |
90 | if(bufl>1024*10) break; // too long... | |
91 | byte[] newbuf=new byte[buf.length*2]; | |
92 | System.arraycopy(buf, 0, newbuf, 0, buf.length); | |
93 | buf=newbuf; | |
94 | } | |
95 | buf[bufl++]=(byte)j; | |
96 | } | |
97 | ||
98 | j=0; | |
99 | while(j<bufl){ | |
100 | i=buf[j]; | |
101 | if(i==' '||i=='\t'){ j++; continue; } | |
102 | if(i=='#'){ | |
103 | addInvalidLine(Util.byte2str(buf, 0, bufl)); | |
104 | continue loop; | |
105 | } | |
106 | break; | |
107 | } | |
108 | if(j>=bufl){ | |
109 | addInvalidLine(Util.byte2str(buf, 0, bufl)); | |
110 | continue loop; | |
111 | } | |
112 | ||
113 | sb.setLength(0); | |
114 | while(j<bufl){ | |
115 | i=buf[j++]; | |
116 | if(i==0x20 || i=='\t'){ break; } | |
117 | sb.append((char)i); | |
118 | } | |
119 | host=sb.toString(); | |
120 | if(j>=bufl || host.length()==0){ | |
121 | addInvalidLine(Util.byte2str(buf, 0, bufl)); | |
122 | continue loop; | |
123 | } | |
124 | ||
125 | sb.setLength(0); | |
126 | type=-1; | |
127 | while(j<bufl){ | |
128 | i=buf[j++]; | |
129 | if(i==0x20 || i=='\t'){ break; } | |
130 | sb.append((char)i); | |
131 | } | |
132 | if(sb.toString().equals("ssh-dss")){ type=HostKey.SSHDSS; } | |
133 | else if(sb.toString().equals("ssh-rsa")){ type=HostKey.SSHRSA; } | |
134 | else { j=bufl; } | |
135 | if(j>=bufl){ | |
136 | addInvalidLine(Util.byte2str(buf, 0, bufl)); | |
137 | continue loop; | |
138 | } | |
139 | ||
140 | sb.setLength(0); | |
141 | while(j<bufl){ | |
142 | i=buf[j++]; | |
143 | if(i==0x0d){ continue; } | |
144 | if(i==0x0a){ break; } | |
145 | sb.append((char)i); | |
146 | } | |
147 | key=sb.toString(); | |
148 | if(key.length()==0){ | |
149 | addInvalidLine(Util.byte2str(buf, 0, bufl)); | |
150 | continue loop; | |
151 | } | |
152 | ||
153 | //System.err.println(host); | |
154 | //System.err.println("|"+key+"|"); | |
155 | ||
156 | HostKey hk = null; | |
157 | hk = new HashedHostKey(host, type, | |
158 | Util.fromBase64(Util.str2byte(key), 0, | |
159 | key.length())); | |
160 | pool.addElement(hk); | |
161 | } | |
162 | fis.close(); | |
163 | if(error){ | |
164 | throw new JSchException("KnownHosts: invalid format"); | |
165 | } | |
166 | } | |
167 | catch(Exception e){ | |
168 | if(e instanceof JSchException) | |
169 | throw (JSchException)e; | |
170 | if(e instanceof Throwable) | |
171 | throw new JSchException(e.toString(), (Throwable)e); | |
172 | throw new JSchException(e.toString()); | |
173 | } | |
174 | } | |
175 | private void addInvalidLine(String line) throws JSchException { | |
176 | HostKey hk = new HostKey(line, HostKey.UNKNOWN, null); | |
177 | pool.addElement(hk); | |
178 | } | |
179 | String getKnownHostsFile(){ return known_hosts; } | |
180 | public String getKnownHostsRepositoryID(){ return known_hosts; } | |
181 | ||
182 | public int check(String host, byte[] key){ | |
183 | int result=NOT_INCLUDED; | |
184 | if(host==null){ | |
185 | return result; | |
186 | } | |
187 | ||
188 | int type=getType(key); | |
189 | HostKey hk; | |
190 | ||
191 | synchronized(pool){ | |
192 | for(int i=0; i<pool.size(); i++){ | |
193 | hk=(HostKey)(pool.elementAt(i)); | |
194 | if(hk.isMatched(host) && hk.type==type){ | |
195 | if(Util.array_equals(hk.key, key)){ | |
196 | return OK; | |
197 | } | |
198 | else{ | |
199 | result=CHANGED; | |
200 | } | |
201 | } | |
202 | } | |
203 | } | |
204 | ||
205 | if(result==NOT_INCLUDED && | |
206 | host.startsWith("[") && | |
207 | host.indexOf("]:")>1 | |
208 | ){ | |
209 | return check(host.substring(1, host.indexOf("]:")), key); | |
210 | } | |
211 | ||
212 | return result; | |
213 | } | |
214 | public void add(HostKey hostkey, UserInfo userinfo){ | |
215 | int type=hostkey.type; | |
216 | String host=hostkey.getHost(); | |
217 | byte[] key=hostkey.key; | |
218 | ||
219 | HostKey hk=null; | |
220 | synchronized(pool){ | |
221 | for(int i=0; i<pool.size(); i++){ | |
222 | hk=(HostKey)(pool.elementAt(i)); | |
223 | if(hk.isMatched(host) && hk.type==type){ | |
224 | /* | |
225 | if(Util.array_equals(hk.key, key)){ return; } | |
226 | if(hk.host.equals(host)){ | |
227 | hk.key=key; | |
228 | return; | |
229 | } | |
230 | else{ | |
231 | hk.host=deleteSubString(hk.host, host); | |
232 | break; | |
233 | } | |
234 | */ | |
235 | } | |
236 | } | |
237 | } | |
238 | ||
239 | hk=hostkey; | |
240 | ||
241 | pool.addElement(hk); | |
242 | ||
243 | String bar=getKnownHostsRepositoryID(); | |
244 | if(bar!=null){ | |
245 | boolean foo=true; | |
246 | File goo=new File(bar); | |
247 | if(!goo.exists()){ | |
248 | foo=false; | |
249 | if(userinfo!=null){ | |
250 | foo=userinfo.promptYesNo(bar+" does not exist.\n"+ | |
251 | "Are you sure you want to create it?" | |
252 | ); | |
253 | goo=goo.getParentFile(); | |
254 | if(foo && goo!=null && !goo.exists()){ | |
255 | foo=userinfo.promptYesNo("The parent directory "+goo+" does not exist.\n"+ | |
256 | "Are you sure you want to create it?" | |
257 | ); | |
258 | if(foo){ | |
259 | if(!goo.mkdirs()){ | |
260 | userinfo.showMessage(goo+" has not been created."); | |
261 | foo=false; | |
262 | } | |
263 | else{ | |
264 | userinfo.showMessage(goo+" has been succesfully created.\nPlease check its access permission."); | |
265 | } | |
266 | } | |
267 | } | |
268 | if(goo==null)foo=false; | |
269 | } | |
270 | } | |
271 | if(foo){ | |
272 | try{ | |
273 | sync(bar); | |
274 | } | |
275 | catch(Exception e){ System.err.println("sync known_hosts: "+e); } | |
276 | } | |
277 | } | |
278 | } | |
279 | ||
280 | public HostKey[] getHostKey(){ | |
281 | return getHostKey(null, null); | |
282 | } | |
283 | public HostKey[] getHostKey(String host, String type){ | |
284 | synchronized(pool){ | |
285 | int count=0; | |
286 | for(int i=0; i<pool.size(); i++){ | |
287 | HostKey hk=(HostKey)pool.elementAt(i); | |
288 | if(hk.type==HostKey.UNKNOWN) continue; | |
289 | if(host==null || | |
290 | (hk.isMatched(host) && | |
291 | (type==null || hk.getType().equals(type)))){ | |
292 | count++; | |
293 | } | |
294 | } | |
295 | if(count==0)return null; | |
296 | HostKey[] foo=new HostKey[count]; | |
297 | int j=0; | |
298 | for(int i=0; i<pool.size(); i++){ | |
299 | HostKey hk=(HostKey)pool.elementAt(i); | |
300 | if(hk.type==HostKey.UNKNOWN) continue; | |
301 | if(host==null || | |
302 | (hk.isMatched(host) && | |
303 | (type==null || hk.getType().equals(type)))){ | |
304 | foo[j++]=hk; | |
305 | } | |
306 | } | |
307 | return foo; | |
308 | } | |
309 | } | |
310 | public void remove(String host, String type){ | |
311 | remove(host, type, null); | |
312 | } | |
313 | public void remove(String host, String type, byte[] key){ | |
314 | boolean sync=false; | |
315 | synchronized(pool){ | |
316 | for(int i=0; i<pool.size(); i++){ | |
317 | HostKey hk=(HostKey)(pool.elementAt(i)); | |
318 | if(host==null || | |
319 | (hk.isMatched(host) && | |
320 | (type==null || (hk.getType().equals(type) && | |
321 | (key==null || Util.array_equals(key, hk.key)))))){ | |
322 | String hosts=hk.getHost(); | |
323 | if(hosts.equals(host) || | |
324 | ((hk instanceof HashedHostKey) && | |
325 | ((HashedHostKey)hk).isHashed())){ | |
326 | pool.removeElement(hk); | |
327 | } | |
328 | else{ | |
329 | hk.host=deleteSubString(hosts, host); | |
330 | } | |
331 | sync=true; | |
332 | } | |
333 | } | |
334 | } | |
335 | if(sync){ | |
336 | try{sync();}catch(Exception e){}; | |
337 | } | |
338 | } | |
339 | ||
340 | protected void sync() throws IOException { | |
341 | if(known_hosts!=null) | |
342 | sync(known_hosts); | |
343 | } | |
344 | protected synchronized void sync(String foo) throws IOException { | |
345 | if(foo==null) return; | |
346 | FileOutputStream fos=new FileOutputStream(foo); | |
347 | dump(fos); | |
348 | fos.close(); | |
349 | } | |
350 | ||
351 | private static final byte[] space={(byte)0x20}; | |
352 | private static final byte[] cr=Util.str2byte("\n"); | |
353 | void dump(OutputStream out) throws IOException { | |
354 | try{ | |
355 | HostKey hk; | |
356 | synchronized(pool){ | |
357 | for(int i=0; i<pool.size(); i++){ | |
358 | hk=(HostKey)(pool.elementAt(i)); | |
359 | //hk.dump(out); | |
360 | String host=hk.getHost(); | |
361 | String type=hk.getType(); | |
362 | if(type.equals("UNKNOWN")){ | |
363 | out.write(Util.str2byte(host)); | |
364 | out.write(cr); | |
365 | continue; | |
366 | } | |
367 | out.write(Util.str2byte(host)); | |
368 | out.write(space); | |
369 | out.write(Util.str2byte(type)); | |
370 | out.write(space); | |
371 | out.write(Util.str2byte(hk.getKey())); | |
372 | out.write(cr); | |
373 | } | |
374 | } | |
375 | } | |
376 | catch(Exception e){ | |
377 | System.err.println(e); | |
378 | } | |
379 | } | |
380 | private int getType(byte[] key){ | |
381 | if(key[8]=='d') return HostKey.SSHDSS; | |
382 | if(key[8]=='r') return HostKey.SSHRSA; | |
383 | return HostKey.UNKNOWN; | |
384 | } | |
385 | private String deleteSubString(String hosts, String host){ | |
386 | int i=0; | |
387 | int hostlen=host.length(); | |
388 | int hostslen=hosts.length(); | |
389 | int j; | |
390 | while(i<hostslen){ | |
391 | j=hosts.indexOf(',', i); | |
392 | if(j==-1) break; | |
393 | if(!host.equals(hosts.substring(i, j))){ | |
394 | i=j+1; | |
395 | continue; | |
396 | } | |
397 | return hosts.substring(0, i)+hosts.substring(j+1); | |
398 | } | |
399 | if(hosts.endsWith(host) && hostslen-i==hostlen){ | |
400 | return hosts.substring(0, (hostlen==hostslen) ? 0 :hostslen-hostlen-1); | |
401 | } | |
402 | return hosts; | |
403 | } | |
404 | ||
405 | private synchronized MAC getHMACSHA1(){ | |
406 | if(hmacsha1==null){ | |
407 | try{ | |
408 | Class c=Class.forName(jsch.getConfig("hmac-sha1")); | |
409 | hmacsha1=(MAC)(c.newInstance()); | |
410 | } | |
411 | catch(Exception e){ | |
412 | System.err.println("hmacsha1: "+e); | |
413 | } | |
414 | } | |
415 | return hmacsha1; | |
416 | } | |
417 | ||
418 | HostKey createHashedHostKey(String host, byte[]key) throws JSchException { | |
419 | HashedHostKey hhk=new HashedHostKey(host, key); | |
420 | hhk.hash(); | |
421 | return hhk; | |
422 | } | |
423 | class HashedHostKey extends HostKey{ | |
424 | private static final String HASH_MAGIC="|1|"; | |
425 | private static final String HASH_DELIM="|"; | |
426 | ||
427 | private boolean hashed=false; | |
428 | byte[] salt=null; | |
429 | byte[] hash=null; | |
430 | ||
431 | ||
432 | HashedHostKey(String host, byte[] key) throws JSchException { | |
433 | this(host, GUESS, key); | |
434 | } | |
435 | HashedHostKey(String host, int type, byte[] key) throws JSchException { | |
436 | super(host, type, key); | |
437 | if(this.host.startsWith(HASH_MAGIC) && | |
438 | this.host.substring(HASH_MAGIC.length()).indexOf(HASH_DELIM)>0){ | |
439 | String data=this.host.substring(HASH_MAGIC.length()); | |
440 | String _salt=data.substring(0, data.indexOf(HASH_DELIM)); | |
441 | String _hash=data.substring(data.indexOf(HASH_DELIM)+1); | |
442 | salt=Util.fromBase64(Util.str2byte(_salt), 0, _salt.length()); | |
443 | hash=Util.fromBase64(Util.str2byte(_hash), 0, _hash.length()); | |
444 | if(salt.length!=20 || // block size of hmac-sha1 | |
445 | hash.length!=20){ | |
446 | salt=null; | |
447 | hash=null; | |
448 | return; | |
449 | } | |
450 | hashed=true; | |
451 | } | |
452 | } | |
453 | ||
454 | boolean isMatched(String _host){ | |
455 | if(!hashed){ | |
456 | return super.isMatched(_host); | |
457 | } | |
458 | MAC macsha1=getHMACSHA1(); | |
459 | try{ | |
460 | synchronized(macsha1){ | |
461 | macsha1.init(salt); | |
462 | byte[] foo=Util.str2byte(_host); | |
463 | macsha1.update(foo, 0, foo.length); | |
464 | byte[] bar=new byte[macsha1.getBlockSize()]; | |
465 | macsha1.doFinal(bar, 0); | |
466 | return Util.array_equals(hash, bar); | |
467 | } | |
468 | } | |
469 | catch(Exception e){ | |
470 | System.out.println(e); | |
471 | } | |
472 | return false; | |
473 | } | |
474 | ||
475 | boolean isHashed(){ | |
476 | return hashed; | |
477 | } | |
478 | ||
479 | void hash(){ | |
480 | if(hashed) | |
481 | return; | |
482 | MAC macsha1=getHMACSHA1(); | |
483 | if(salt==null){ | |
484 | Random random=Session.random; | |
485 | synchronized(random){ | |
486 | salt=new byte[macsha1.getBlockSize()]; | |
487 | random.fill(salt, 0, salt.length); | |
488 | } | |
489 | } | |
490 | try{ | |
491 | synchronized(macsha1){ | |
492 | macsha1.init(salt); | |
493 | byte[] foo=Util.str2byte(host); | |
494 | macsha1.update(foo, 0, foo.length); | |
495 | hash=new byte[macsha1.getBlockSize()]; | |
496 | macsha1.doFinal(hash, 0); | |
497 | } | |
498 | } | |
499 | catch(Exception e){ | |
500 | } | |
501 | host=HASH_MAGIC+Util.byte2str(Util.toBase64(salt, 0, salt.length))+ | |
502 | HASH_DELIM+Util.byte2str(Util.toBase64(hash, 0, hash.length)); | |
503 | hashed=true; | |
504 | } | |
505 | } | |
506 | } |