1 /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
3 Copyright (c) 2002-2010 ymnk, JCraft,Inc. All rights reserved.
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
8 1. Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
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.
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.
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.
30 package com.jcraft.jsch;
32 import java.io.FileOutputStream;
33 import java.io.FileInputStream;
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;
42 static final int VENDOR_OPENSSH=0;
43 static final int VENDOR_FSECURE=1;
44 int vendor=VENDOR_OPENSSH;
46 private static final byte[] cr=Util.str2byte("\n");
48 public static KeyPair genKeyPair(JSch jsch, int type) throws JSchException{
49 return genKeyPair(jsch, type, 1024);
51 public static KeyPair genKeyPair(JSch jsch, int type, int key_size) throws JSchException{
53 if(type==DSA){ kpair=new KeyPairDSA(jsch); }
54 else if(type==RSA){ kpair=new KeyPairRSA(jsch); }
56 kpair.generate(key_size);
61 abstract void generate(int key_size) throws JSchException;
63 abstract byte[] getBegin();
64 abstract byte[] getEnd();
65 abstract int getKeySize();
68 private Cipher cipher;
70 private Random random;
72 private byte[] passphrase;
74 public KeyPair(JSch jsch){
78 static byte[][] header={Util.str2byte("Proc-Type: 4,ENCRYPTED"),
79 Util.str2byte("DEK-Info: DES-EDE3-CBC,")};
81 abstract byte[] getPrivateKey();
83 public void writePrivateKey(java.io.OutputStream out){
84 byte[] plain=getPrivateKey();
85 byte[][] _iv=new byte[1][];
86 byte[] encoded=encrypt(plain, _iv);
90 byte[] prv=Util.toBase64(encoded, 0, encoded.length);
93 out.write(getBegin()); out.write(cr);
95 out.write(header[0]); out.write(cr);
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)));
107 out.write(prv, i, 64);
112 out.write(prv, i, prv.length-i);
116 out.write(getEnd()); out.write(cr);
123 private static byte[] space=Util.str2byte(" ");
125 abstract byte[] getKeyTypeName();
126 public abstract int getKeyType();
128 public byte[] getPublicKeyBlob(){ return publickeyblob; }
130 public void writePublicKey(java.io.OutputStream out, String comment){
131 byte[] pubblob=getPublicKeyBlob();
132 byte[] pub=Util.toBase64(pubblob, 0, pubblob.length);
134 out.write(getKeyTypeName()); out.write(space);
135 out.write(pub, 0, pub.length); out.write(space);
136 out.write(Util.str2byte(comment));
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);
149 public void writeSECSHPublicKey(java.io.OutputStream out, String comment){
150 byte[] pubblob=getPublicKeyBlob();
151 byte[] pub=Util.toBase64(pubblob, 0, pubblob.length);
153 out.write(Util.str2byte("---- BEGIN SSH2 PUBLIC KEY ----")); out.write(cr);
154 out.write(Util.str2byte("Comment: \""+comment+"\"")); out.write(cr);
156 while(index<pub.length){
158 if((pub.length-index)<len)len=pub.length-index;
159 out.write(pub, index, len); out.write(cr);
162 out.write(Util.str2byte("---- END SSH2 PUBLIC KEY ----")); out.write(cr);
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);
175 public void writePrivateKey(String name) throws java.io.FileNotFoundException, java.io.IOException{
176 FileOutputStream fos=new FileOutputStream(name);
177 writePrivateKey(fos);
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);
188 private byte[] encrypt(byte[] plain, byte[][] _iv){
189 if(passphrase==null) return plain;
191 if(cipher==null) cipher=genCipher();
192 byte[] iv=_iv[0]=new byte[cipher.getIVSize()];
194 if(random==null) random=genRandom();
195 random.fill(iv, 0, iv.length);
197 byte[] key=genKey(passphrase, iv);
198 byte[] encoded=plain;
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;
214 cipher.init(Cipher.ENCRYPT_MODE, key, iv);
215 cipher.update(encoded, 0, encoded.length, encoded, 0);
218 //System.err.println(e);
224 abstract boolean parse(byte[] data);
226 private byte[] decrypt(byte[] data, byte[] passphrase, byte[] iv){
228 if(iv==null){ // FSecure
230 for(int i=0; i<iv.length; i++)iv[i]=0;
234 byte[] key=genKey(passphrase, iv);
235 cipher.init(Cipher.DECRYPT_MODE, key, iv);
237 byte[] plain=new byte[data.length];
238 cipher.update(data, 0, data.length, plain, 0);
242 //System.err.println(e);
247 int writeSEQUENCE(byte[] buf, int index, int len){
249 index=writeLength(buf, index, len);
252 int writeINTEGER(byte[] buf, int index, byte[] data){
254 index=writeLength(buf, index, data.length);
255 System.arraycopy(data, 0, buf, index, data.length);
260 int countLength(int len){
262 if(len<=0x7f) return i;
270 int writeLength(byte[] data, int index, int len){
271 int i=countLength(len)-1;
273 data[index++]=(byte)len;
276 data[index++]=(byte)(0x80|i);
279 data[index+i-1]=(byte)(len&0xff);
286 private Random genRandom(){
289 Class c=Class.forName(jsch.getConfig("random"));
290 random=(Random)(c.newInstance());
292 catch(Exception e){ System.err.println("connect: random "+e); }
297 private HASH genHash(){
299 Class c=Class.forName(jsch.getConfig("md5"));
300 hash=(HASH)(c.newInstance());
307 private Cipher genCipher(){
310 c=Class.forName(jsch.getConfig("3des-cbc"));
311 cipher=(Cipher)(c.newInstance());
320 h(0) <- hash(passphrase, iv);
321 h(n) <- hash(h(n-1), passphrase, iv);
322 key <- (h(0),...,h(n))[0,..,key.length];
324 synchronized byte[] genKey(byte[] passphrase, byte[] iv){
325 if(cipher==null) cipher=genCipher();
326 if(hash==null) hash=genHash();
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)];
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);
340 System.arraycopy(tmp, 0, hn, index, tmp.length);
343 System.arraycopy(hn, 0, key, 0, key.length);
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);
350 System.arraycopy(tmp, 0, hn, index, tmp.length);
353 System.arraycopy(hn, 0, key, 0, key.length);
357 System.err.println(e);
362 public void setPassphrase(String passphrase){
363 if(passphrase==null || passphrase.length()==0){
364 setPassphrase((byte[])null);
367 setPassphrase(Util.str2byte(passphrase));
370 public void setPassphrase(byte[] passphrase){
371 if(passphrase!=null && passphrase.length==0)
373 this.passphrase=passphrase;
376 private boolean encrypted=false;
377 private byte[] data=null;
378 private byte[] iv=null;
379 private byte[] publickeyblob=null;
381 public boolean isEncrypted(){ return encrypted; }
382 public boolean decrypt(String _passphrase){
383 if(_passphrase==null || _passphrase.length()==0){
386 return decrypt(Util.str2byte(_passphrase));
388 public boolean decrypt(byte[] _passphrase){
392 if(_passphrase==null){
395 byte[] bar=new byte[_passphrase.length];
396 System.arraycopy(_passphrase, 0, bar, 0, bar.length);
398 byte[] foo=decrypt(data, _passphrase, iv);
399 Util.bzero(_passphrase);
406 public static KeyPair load(JSch jsch, String prvkey) throws JSchException{
407 String pubkey=prvkey+".pub";
408 if(!new File(pubkey).exists()){
411 return load(jsch, prvkey, pubkey);
413 public static KeyPair load(JSch jsch, String prvkey, String pubkey) throws JSchException{
415 byte[] iv=new byte[8]; // 8
416 boolean encrypted=true;
419 byte[] publickeyblob=null;
422 int vendor=VENDOR_OPENSSH;
425 File file=new File(prvkey);
426 FileInputStream fis=new FileInputStream(prvkey);
427 byte[] buf=new byte[(int)(file.length())];
430 int i=fis.read(buf, len, buf.length-len);
440 if(buf[i]=='B'&& buf[i+1]=='E'&& buf[i+2]=='G'&& buf[i+3]=='I'){
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
446 vendor=VENDOR_FSECURE;
449 //System.err.println("invalid format: "+identity);
450 throw new JSchException("invalid privatekey: "+prvkey);
455 if(buf[i]=='C'&& buf[i+1]=='B'&& buf[i+2]=='C'&& buf[i+3]==','){
457 for(int ii=0; ii<iv.length; ii++){
458 iv[ii]=(byte)(((a2b(buf[i++])<<4)&0xf0)+(a2b(buf[i++])&0xf));
463 i+1<buf.length && buf[i+1]==0x0a){
467 if(buf[i]==0x0a && i+1<buf.length){
468 if(buf[i+1]==0x0a){ i+=2; break; }
470 i+2<buf.length && buf[i+2]==0x0a){
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;}
481 encrypted=false; // no passphrase
489 throw new JSchException("invalid privatekey: "+prvkey);
495 boolean xd=(buf[i-1]==0x0d);
496 System.arraycopy(buf, i+1,
505 if(buf[i]=='-'){ break; }
508 data=Util.fromBase64(buf, start, i-start);
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){
516 Buffer _buf=new Buffer(data);
517 _buf.getInt(); // 0x3f6ff9be
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")){
526 byte[] foo=new byte[data.length-_buf.getOffSet()];
530 throw new JSchException("unknown privatekey format: "+prvkey);
532 else if(cipher.equals("none")){
538 byte[] foo=new byte[data.length-_buf.getOffSet()];
546 file=new File(pubkey);
547 fis=new FileInputStream(pubkey);
548 buf=new byte[(int)(file.length())];
551 i=fis.read(buf, len, buf.length-len);
558 if(buf.length>4 && // FSecure's public key
559 buf[0]=='-' && buf[1]=='-' && buf[2]=='-' && buf[3]=='-'){
563 do{i++;}while(buf.length>i && buf[i]!=0x0a);
564 if(buf.length<=i) {valid=false;}
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;}
580 if(buf.length<=i){valid=false;}
583 while(valid && i<len){
585 System.arraycopy(buf, i+1, buf, i, len-i-1);
589 if(buf[i]=='-'){ break; }
593 publickeyblob=Util.fromBase64(buf, start, i-start);
595 if(publickeyblob[8]=='d'){ type=DSA; }
596 else if(publickeyblob[8]=='r'){ type=RSA; }
601 if(buf[0]=='s'&& buf[1]=='s'&& buf[2]=='h' && buf[3]=='-'){
603 while(i<len){ if(buf[i]==' ')break; i++;} i++;
606 while(i<len){ if(buf[i]==' ')break; i++;}
607 publickeyblob=Util.fromBase64(buf, start, i-start);
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());
624 if(type==DSA){ kpair=new KeyPairDSA(jsch); }
625 else if(type==RSA){ kpair=new KeyPairRSA(jsch); }
628 kpair.encrypted=encrypted;
629 kpair.publickeyblob=publickeyblob;
637 if(kpair.parse(data)){
641 throw new JSchException("invalid privatekey: "+prvkey);
649 static private byte a2b(byte c){
650 if('0'<=c&&c<='9') return (byte)(c-'0');
651 return (byte)(c-'a'+10);
653 static private byte b2a(byte c){
654 if(0<=c&&c<=9) return (byte)(c+'0');
655 return (byte)(c-10+'A');
658 public void dispose(){
659 Util.bzero(passphrase);
662 public void finalize (){