]> Joshua Wise's Git repositories - dumload.git/blob - src/com/jcraft/jsch/KeyPair.java
A few icon tweaks
[dumload.git] / src / com / jcraft / jsch / KeyPair.java
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.FileOutputStream;
33 import java.io.FileInputStream;
34 import java.io.File;
35
36 public abstract class KeyPair{
37   public static final int ERROR=0;
38   public static final int DSA=1;
39   public static final int RSA=2;
40   public static final int UNKNOWN=3;
41
42   static final int VENDOR_OPENSSH=0;
43   static final int VENDOR_FSECURE=1;
44   int vendor=VENDOR_OPENSSH;
45
46   private static final byte[] cr=Util.str2byte("\n");
47
48   public static KeyPair genKeyPair(JSch jsch, int type) throws JSchException{
49     return genKeyPair(jsch, type, 1024);
50   }
51   public static KeyPair genKeyPair(JSch jsch, int type, int key_size) throws JSchException{
52     KeyPair kpair=null;
53     if(type==DSA){ kpair=new KeyPairDSA(jsch); }
54     else if(type==RSA){ kpair=new KeyPairRSA(jsch); }
55     if(kpair!=null){
56       kpair.generate(key_size);
57     }
58     return kpair;
59   }
60
61   abstract void generate(int key_size) throws JSchException;
62
63   abstract byte[] getBegin();
64   abstract byte[] getEnd();
65   abstract int getKeySize();
66
67   JSch jsch=null;
68   private Cipher cipher;
69   private HASH hash;
70   private Random random;
71
72   private byte[] passphrase;
73
74   public KeyPair(JSch jsch){
75     this.jsch=jsch;
76   }
77
78   static byte[][] header={Util.str2byte("Proc-Type: 4,ENCRYPTED"),
79                           Util.str2byte("DEK-Info: DES-EDE3-CBC,")};
80
81   abstract byte[] getPrivateKey();
82
83   public void writePrivateKey(java.io.OutputStream out){
84     byte[] plain=getPrivateKey();
85     byte[][] _iv=new byte[1][];
86     byte[] encoded=encrypt(plain, _iv);
87     if(encoded!=plain)
88       Util.bzero(plain);
89     byte[] iv=_iv[0];
90     byte[] prv=Util.toBase64(encoded, 0, encoded.length);
91
92     try{
93       out.write(getBegin()); out.write(cr);
94       if(passphrase!=null){
95         out.write(header[0]); out.write(cr);
96         out.write(header[1]); 
97         for(int i=0; i<iv.length; i++){
98           out.write(b2a((byte)((iv[i]>>>4)&0x0f)));
99           out.write(b2a((byte)(iv[i]&0x0f)));
100         }
101         out.write(cr);
102         out.write(cr);
103       }
104       int i=0;
105       while(i<prv.length){
106         if(i+64<prv.length){
107           out.write(prv, i, 64);
108           out.write(cr);
109           i+=64;
110           continue;
111         }
112         out.write(prv, i, prv.length-i);
113         out.write(cr);
114         break;
115       }
116       out.write(getEnd()); out.write(cr);
117       //out.close();
118     }
119     catch(Exception e){
120     }
121   }
122
123   private static byte[] space=Util.str2byte(" ");
124
125   abstract byte[] getKeyTypeName();
126   public abstract int getKeyType();
127
128   public byte[] getPublicKeyBlob(){ return publickeyblob; }
129
130   public void writePublicKey(java.io.OutputStream out, String comment){
131     byte[] pubblob=getPublicKeyBlob();
132     byte[] pub=Util.toBase64(pubblob, 0, pubblob.length);
133     try{
134       out.write(getKeyTypeName()); out.write(space);
135       out.write(pub, 0, pub.length); out.write(space);
136       out.write(Util.str2byte(comment));
137       out.write(cr);
138     }
139     catch(Exception e){
140     }
141   }
142
143   public void writePublicKey(String name, String comment) throws java.io.FileNotFoundException, java.io.IOException{
144     FileOutputStream fos=new FileOutputStream(name);
145     writePublicKey(fos, comment);
146     fos.close();
147   }
148
149   public void writeSECSHPublicKey(java.io.OutputStream out, String comment){
150     byte[] pubblob=getPublicKeyBlob();
151     byte[] pub=Util.toBase64(pubblob, 0, pubblob.length);
152     try{
153       out.write(Util.str2byte("---- BEGIN SSH2 PUBLIC KEY ----")); out.write(cr);
154       out.write(Util.str2byte("Comment: \""+comment+"\"")); out.write(cr);
155       int index=0;
156       while(index<pub.length){
157         int len=70;
158         if((pub.length-index)<len)len=pub.length-index;
159         out.write(pub, index, len); out.write(cr);
160         index+=len;
161       }
162       out.write(Util.str2byte("---- END SSH2 PUBLIC KEY ----")); out.write(cr);
163     }
164     catch(Exception e){
165     }
166   }
167
168   public void writeSECSHPublicKey(String name, String comment) throws java.io.FileNotFoundException, java.io.IOException{
169     FileOutputStream fos=new FileOutputStream(name);
170     writeSECSHPublicKey(fos, comment);
171     fos.close();
172   }
173
174
175   public void writePrivateKey(String name) throws java.io.FileNotFoundException, java.io.IOException{
176     FileOutputStream fos=new FileOutputStream(name);
177     writePrivateKey(fos);
178     fos.close();
179   }
180
181   public String getFingerPrint(){
182     if(hash==null) hash=genHash();
183     byte[] kblob=getPublicKeyBlob();
184     if(kblob==null) return null;
185     return getKeySize()+" "+Util.getFingerPrint(hash, kblob);
186   }
187
188   private byte[] encrypt(byte[] plain, byte[][] _iv){
189     if(passphrase==null) return plain;
190
191     if(cipher==null) cipher=genCipher();
192     byte[] iv=_iv[0]=new byte[cipher.getIVSize()];
193
194     if(random==null) random=genRandom();
195     random.fill(iv, 0, iv.length);
196
197     byte[] key=genKey(passphrase, iv);
198     byte[] encoded=plain;
199
200     // PKCS#5Padding
201     {
202       //int bsize=cipher.getBlockSize();
203       int bsize=cipher.getIVSize();
204       byte[] foo=new byte[(encoded.length/bsize+1)*bsize];
205       System.arraycopy(encoded, 0, foo, 0, encoded.length);
206       int padding=bsize-encoded.length%bsize;
207       for(int i=foo.length-1; (foo.length-padding)<=i; i--){
208         foo[i]=(byte)padding;
209       }
210       encoded=foo;
211     }
212
213     try{
214       cipher.init(Cipher.ENCRYPT_MODE, key, iv);
215       cipher.update(encoded, 0, encoded.length, encoded, 0);
216     }
217     catch(Exception e){
218       //System.err.println(e);
219     }
220     Util.bzero(key);
221     return encoded;
222   }
223
224   abstract boolean parse(byte[] data);
225
226   private byte[] decrypt(byte[] data, byte[] passphrase, byte[] iv){
227     /*
228     if(iv==null){  // FSecure
229       iv=new byte[8];
230       for(int i=0; i<iv.length; i++)iv[i]=0;
231     }
232     */
233     try{
234       byte[] key=genKey(passphrase, iv);
235       cipher.init(Cipher.DECRYPT_MODE, key, iv);
236       Util.bzero(key);
237       byte[] plain=new byte[data.length];
238       cipher.update(data, 0, data.length, plain, 0);
239       return plain;
240     }
241     catch(Exception e){
242       //System.err.println(e);
243     }
244     return null;
245   }
246
247   int writeSEQUENCE(byte[] buf, int index, int len){
248     buf[index++]=0x30;
249     index=writeLength(buf, index, len);
250     return index;
251   }
252   int writeINTEGER(byte[] buf, int index, byte[] data){
253     buf[index++]=0x02;
254     index=writeLength(buf, index, data.length);
255     System.arraycopy(data, 0, buf, index, data.length);
256     index+=data.length;
257     return index;
258   }
259
260   int countLength(int len){
261     int i=1;
262     if(len<=0x7f) return i;
263     while(len>0){
264       len>>>=8;
265       i++;
266     }
267     return i;
268   }
269
270   int writeLength(byte[] data, int index, int len){
271     int i=countLength(len)-1;
272     if(i==0){
273       data[index++]=(byte)len;
274       return index;
275     }
276     data[index++]=(byte)(0x80|i);
277     int j=index+i;
278     while(i>0){
279       data[index+i-1]=(byte)(len&0xff);
280       len>>>=8;
281       i--;
282     }
283     return j;
284   }
285
286   private Random genRandom(){
287     if(random==null){
288       try{
289         Class c=Class.forName(jsch.getConfig("random"));
290         random=(Random)(c.newInstance());
291       }
292       catch(Exception e){ System.err.println("connect: random "+e); }
293     }
294     return random;
295   }
296
297   private HASH genHash(){
298     try{
299       Class c=Class.forName(jsch.getConfig("md5"));
300       hash=(HASH)(c.newInstance());
301       hash.init();
302     }
303     catch(Exception e){
304     }
305     return hash;
306   }
307   private Cipher genCipher(){
308     try{
309       Class c;
310       c=Class.forName(jsch.getConfig("3des-cbc"));
311       cipher=(Cipher)(c.newInstance());
312     }
313     catch(Exception e){
314     }
315     return cipher;
316   }
317
318   /*
319     hash is MD5
320     h(0) <- hash(passphrase, iv);
321     h(n) <- hash(h(n-1), passphrase, iv);
322     key <- (h(0),...,h(n))[0,..,key.length];
323   */
324   synchronized byte[] genKey(byte[] passphrase, byte[] iv){
325     if(cipher==null) cipher=genCipher();
326     if(hash==null) hash=genHash();
327
328     byte[] key=new byte[cipher.getBlockSize()];
329     int hsize=hash.getBlockSize();
330     byte[] hn=new byte[key.length/hsize*hsize+
331                        (key.length%hsize==0?0:hsize)];
332     try{
333       byte[] tmp=null;
334       if(vendor==VENDOR_OPENSSH){
335         for(int index=0; index+hsize<=hn.length;){
336           if(tmp!=null){ hash.update(tmp, 0, tmp.length); }
337           hash.update(passphrase, 0, passphrase.length);
338           hash.update(iv, 0, iv.length);
339           tmp=hash.digest();
340           System.arraycopy(tmp, 0, hn, index, tmp.length);
341           index+=tmp.length;
342         }
343         System.arraycopy(hn, 0, key, 0, key.length); 
344       }
345       else if(vendor==VENDOR_FSECURE){
346         for(int index=0; index+hsize<=hn.length;){
347           if(tmp!=null){ hash.update(tmp, 0, tmp.length); }
348           hash.update(passphrase, 0, passphrase.length);
349           tmp=hash.digest();
350           System.arraycopy(tmp, 0, hn, index, tmp.length);
351           index+=tmp.length;
352         }
353         System.arraycopy(hn, 0, key, 0, key.length); 
354       }
355     }
356     catch(Exception e){
357       System.err.println(e);
358     }
359     return key;
360   } 
361
362   public void setPassphrase(String passphrase){
363     if(passphrase==null || passphrase.length()==0){
364       setPassphrase((byte[])null);
365     }
366     else{
367       setPassphrase(Util.str2byte(passphrase));
368     }
369   }
370   public void setPassphrase(byte[] passphrase){
371     if(passphrase!=null && passphrase.length==0) 
372       passphrase=null;
373     this.passphrase=passphrase;
374   }
375
376   private boolean encrypted=false;
377   private byte[] data=null;
378   private byte[] iv=null;
379   private byte[] publickeyblob=null;
380
381   public boolean isEncrypted(){ return encrypted; }
382   public boolean decrypt(String _passphrase){
383     if(_passphrase==null || _passphrase.length()==0){
384       return !encrypted;
385     }
386     return decrypt(Util.str2byte(_passphrase));
387   }
388   public boolean decrypt(byte[] _passphrase){
389     if(!encrypted){
390       return true;
391     }
392     if(_passphrase==null){
393       return !encrypted;
394     }
395     byte[] bar=new byte[_passphrase.length];
396     System.arraycopy(_passphrase, 0, bar, 0, bar.length);
397     _passphrase=bar;
398     byte[] foo=decrypt(data, _passphrase, iv);
399     Util.bzero(_passphrase);
400     if(parse(foo)){
401       encrypted=false;
402     }
403     return !encrypted;
404   }
405
406   public static KeyPair load(JSch jsch, String prvkey) throws JSchException{
407     String pubkey=prvkey+".pub";
408     if(!new File(pubkey).exists()){
409       pubkey=null;
410     }
411     return load(jsch, prvkey, pubkey);
412   }
413   public static KeyPair load(JSch jsch, String prvkey, String pubkey) throws JSchException{
414
415     byte[] iv=new byte[8];       // 8
416     boolean encrypted=true;
417     byte[] data=null;
418
419     byte[] publickeyblob=null;
420
421     int type=ERROR;
422     int vendor=VENDOR_OPENSSH;
423
424     try{
425       File file=new File(prvkey);
426       FileInputStream fis=new FileInputStream(prvkey);
427       byte[] buf=new byte[(int)(file.length())];
428       int len=0;
429       while(true){
430         int i=fis.read(buf, len, buf.length-len);
431         if(i<=0)
432           break;
433         len+=i;
434       }
435       fis.close();
436
437       int i=0;
438
439       while(i<len){
440         if(buf[i]=='B'&& buf[i+1]=='E'&& buf[i+2]=='G'&& buf[i+3]=='I'){
441           i+=6;     
442           if(buf[i]=='D'&& buf[i+1]=='S'&& buf[i+2]=='A'){ type=DSA; }
443           else if(buf[i]=='R'&& buf[i+1]=='S'&& buf[i+2]=='A'){ type=RSA; }
444           else if(buf[i]=='S'&& buf[i+1]=='S'&& buf[i+2]=='H'){ // FSecure
445             type=UNKNOWN;
446             vendor=VENDOR_FSECURE;
447           }
448           else{
449             //System.err.println("invalid format: "+identity);
450             throw new JSchException("invalid privatekey: "+prvkey);
451           }
452           i+=3;
453           continue;
454         }
455         if(buf[i]=='C'&& buf[i+1]=='B'&& buf[i+2]=='C'&& buf[i+3]==','){
456           i+=4;
457           for(int ii=0; ii<iv.length; ii++){
458             iv[ii]=(byte)(((a2b(buf[i++])<<4)&0xf0)+(a2b(buf[i++])&0xf));
459           }
460           continue;
461         }
462         if(buf[i]==0x0d &&
463            i+1<buf.length && buf[i+1]==0x0a){
464           i++;
465           continue;
466         }
467         if(buf[i]==0x0a && i+1<buf.length){
468           if(buf[i+1]==0x0a){ i+=2; break; }
469           if(buf[i+1]==0x0d &&
470              i+2<buf.length && buf[i+2]==0x0a){
471              i+=3; break;
472           }
473           boolean inheader=false;
474           for(int j=i+1; j<buf.length; j++){
475             if(buf[j]==0x0a) break;
476             //if(buf[j]==0x0d) break;
477             if(buf[j]==':'){inheader=true; break;}
478           }
479           if(!inheader){
480             i++; 
481             encrypted=false;    // no passphrase
482             break;
483           }
484         }
485         i++;
486       }
487
488       if(type==ERROR){
489         throw new JSchException("invalid privatekey: "+prvkey);
490       }
491
492       int start=i;
493       while(i<len){
494         if(buf[i]==0x0a){
495           boolean xd=(buf[i-1]==0x0d);
496           System.arraycopy(buf, i+1, 
497                            buf, 
498                            i-(xd ? 1 : 0), 
499                            len-i-1-(xd ? 1 : 0)
500                            );
501           if(xd)len--;
502           len--;
503           continue;
504         }
505         if(buf[i]=='-'){  break; }
506         i++;
507       }
508       data=Util.fromBase64(buf, start, i-start);
509
510       if(data.length>4 &&            // FSecure
511          data[0]==(byte)0x3f &&
512          data[1]==(byte)0x6f &&
513          data[2]==(byte)0xf9 &&
514          data[3]==(byte)0xeb){
515
516         Buffer _buf=new Buffer(data);
517         _buf.getInt();  // 0x3f6ff9be
518         _buf.getInt();
519         byte[]_type=_buf.getString();
520         //System.err.println("type: "+new String(_type)); 
521         byte[] _cipher=_buf.getString();
522         String cipher=Util.byte2str(_cipher);
523         //System.err.println("cipher: "+cipher); 
524         if(cipher.equals("3des-cbc")){
525            _buf.getInt();
526            byte[] foo=new byte[data.length-_buf.getOffSet()];
527            _buf.getByte(foo);
528            data=foo;
529            encrypted=true;
530            throw new JSchException("unknown privatekey format: "+prvkey);
531         }
532         else if(cipher.equals("none")){
533            _buf.getInt();
534            _buf.getInt();
535
536            encrypted=false;
537
538            byte[] foo=new byte[data.length-_buf.getOffSet()];
539            _buf.getByte(foo);
540            data=foo;
541         }
542       }
543
544       if(pubkey!=null){
545         try{
546           file=new File(pubkey);
547           fis=new FileInputStream(pubkey);
548           buf=new byte[(int)(file.length())];
549           len=0;
550           while(true){
551             i=fis.read(buf, len, buf.length-len);
552             if(i<=0)
553               break;
554             len+=i;
555           }
556           fis.close();
557
558           if(buf.length>4 &&             // FSecure's public key
559              buf[0]=='-' && buf[1]=='-' && buf[2]=='-' && buf[3]=='-'){
560
561             boolean valid=true;
562             i=0;
563             do{i++;}while(buf.length>i && buf[i]!=0x0a);
564             if(buf.length<=i) {valid=false;}
565
566             while(valid){
567               if(buf[i]==0x0a){
568                 boolean inheader=false;
569                 for(int j=i+1; j<buf.length; j++){
570                   if(buf[j]==0x0a) break;
571                   if(buf[j]==':'){inheader=true; break;}
572                 }
573                 if(!inheader){
574                   i++; 
575                   break;
576                 }
577               }
578               i++;
579             }
580             if(buf.length<=i){valid=false;}
581
582             start=i;
583             while(valid && i<len){
584               if(buf[i]==0x0a){
585                 System.arraycopy(buf, i+1, buf, i, len-i-1);
586                 len--;
587                 continue;
588               }
589               if(buf[i]=='-'){  break; }
590               i++;
591             }
592             if(valid){
593               publickeyblob=Util.fromBase64(buf, start, i-start);
594               if(type==UNKNOWN){
595                 if(publickeyblob[8]=='d'){ type=DSA; }
596                 else if(publickeyblob[8]=='r'){ type=RSA; }
597               }
598             }
599           }
600           else{
601             if(buf[0]=='s'&& buf[1]=='s'&& buf[2]=='h' && buf[3]=='-'){
602               i=0;
603               while(i<len){ if(buf[i]==' ')break; i++;} i++;
604               if(i<len){
605                 start=i;
606                 while(i<len){ if(buf[i]==' ')break; i++;}
607                 publickeyblob=Util.fromBase64(buf, start, i-start);
608               }
609             }
610           }
611         }
612         catch(Exception ee){
613         }
614       }
615     }
616     catch(Exception e){
617       if(e instanceof JSchException) throw (JSchException)e;
618       if(e instanceof Throwable)
619         throw new JSchException(e.toString(), (Throwable)e);
620       throw new JSchException(e.toString());
621     }
622
623     KeyPair kpair=null;
624     if(type==DSA){ kpair=new KeyPairDSA(jsch); }
625     else if(type==RSA){ kpair=new KeyPairRSA(jsch); }
626
627     if(kpair!=null){
628       kpair.encrypted=encrypted;
629       kpair.publickeyblob=publickeyblob;
630       kpair.vendor=vendor;
631
632       if(encrypted){
633         kpair.iv=iv;
634         kpair.data=data;
635       }
636       else{
637         if(kpair.parse(data)){
638           return kpair;
639         }
640         else{
641           throw new JSchException("invalid privatekey: "+prvkey);
642         }
643       }
644     }
645
646     return kpair;
647   }
648
649   static private byte a2b(byte c){
650     if('0'<=c&&c<='9') return (byte)(c-'0');
651     return (byte)(c-'a'+10);
652   }
653   static private byte b2a(byte c){
654     if(0<=c&&c<=9) return (byte)(c+'0');
655     return (byte)(c-10+'A');
656   }
657
658   public void dispose(){
659     Util.bzero(passphrase);
660   }
661
662   public void finalize (){
663     dispose();
664   }
665 }
This page took 0.054233 seconds and 4 git commands to generate.