diff options
Diffstat (limited to 'java/com/jcraft/jsch/KeyPair.java')
-rw-r--r-- | java/com/jcraft/jsch/KeyPair.java | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/java/com/jcraft/jsch/KeyPair.java b/java/com/jcraft/jsch/KeyPair.java new file mode 100644 index 00000000..b3f681cb --- /dev/null +++ b/java/com/jcraft/jsch/KeyPair.java @@ -0,0 +1,729 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2002-2012 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.File; + +public abstract class KeyPair{ + public static final int ERROR=0; + public static final int DSA=1; + public static final int RSA=2; + public static final int UNKNOWN=3; + + static final int VENDOR_OPENSSH=0; + static final int VENDOR_FSECURE=1; + int vendor=VENDOR_OPENSSH; + + private static final byte[] cr=Util.str2byte("\n"); + + public static KeyPair genKeyPair(JSch jsch, int type) throws JSchException{ + return genKeyPair(jsch, type, 1024); + } + public static KeyPair genKeyPair(JSch jsch, int type, int key_size) throws JSchException{ + KeyPair kpair=null; + if(type==DSA){ kpair=new KeyPairDSA(jsch); } + else if(type==RSA){ kpair=new KeyPairRSA(jsch); } + if(kpair!=null){ + kpair.generate(key_size); + } + return kpair; + } + + abstract void generate(int key_size) throws JSchException; + + abstract byte[] getBegin(); + abstract byte[] getEnd(); + abstract int getKeySize(); + + public String getPublicKeyComment(){ + return publicKeyComment; + } + private String publicKeyComment = ""; + + JSch jsch=null; + private Cipher cipher; + private HASH hash; + private Random random; + + private byte[] passphrase; + + public KeyPair(JSch jsch){ + this.jsch=jsch; + } + + static byte[][] header={Util.str2byte("Proc-Type: 4,ENCRYPTED"), + Util.str2byte("DEK-Info: DES-EDE3-CBC,")}; + + abstract byte[] getPrivateKey(); + + public void writePrivateKey(java.io.OutputStream out){ + byte[] plain=getPrivateKey(); + byte[][] _iv=new byte[1][]; + byte[] encoded=encrypt(plain, _iv); + if(encoded!=plain) + Util.bzero(plain); + byte[] iv=_iv[0]; + byte[] prv=Util.toBase64(encoded, 0, encoded.length); + + try{ + out.write(getBegin()); out.write(cr); + if(passphrase!=null){ + out.write(header[0]); out.write(cr); + out.write(header[1]); + for(int i=0; i<iv.length; i++){ + out.write(b2a((byte)((iv[i]>>>4)&0x0f))); + out.write(b2a((byte)(iv[i]&0x0f))); + } + out.write(cr); + out.write(cr); + } + int i=0; + while(i<prv.length){ + if(i+64<prv.length){ + out.write(prv, i, 64); + out.write(cr); + i+=64; + continue; + } + out.write(prv, i, prv.length-i); + out.write(cr); + break; + } + out.write(getEnd()); out.write(cr); + //out.close(); + } + catch(Exception e){ + } + } + + private static byte[] space=Util.str2byte(" "); + + abstract byte[] getKeyTypeName(); + public abstract int getKeyType(); + + public byte[] getPublicKeyBlob(){ return publickeyblob; } + + public void writePublicKey(java.io.OutputStream out, String comment){ + byte[] pubblob=getPublicKeyBlob(); + byte[] pub=Util.toBase64(pubblob, 0, pubblob.length); + try{ + out.write(getKeyTypeName()); out.write(space); + out.write(pub, 0, pub.length); out.write(space); + out.write(Util.str2byte(comment)); + out.write(cr); + } + catch(Exception e){ + } + } + + public void writePublicKey(String name, String comment) throws java.io.FileNotFoundException, java.io.IOException{ + FileOutputStream fos=new FileOutputStream(name); + writePublicKey(fos, comment); + fos.close(); + } + + public void writeSECSHPublicKey(java.io.OutputStream out, String comment){ + byte[] pubblob=getPublicKeyBlob(); + byte[] pub=Util.toBase64(pubblob, 0, pubblob.length); + try{ + out.write(Util.str2byte("---- BEGIN SSH2 PUBLIC KEY ----")); out.write(cr); + out.write(Util.str2byte("Comment: \""+comment+"\"")); out.write(cr); + int index=0; + while(index<pub.length){ + int len=70; + if((pub.length-index)<len)len=pub.length-index; + out.write(pub, index, len); out.write(cr); + index+=len; + } + out.write(Util.str2byte("---- END SSH2 PUBLIC KEY ----")); out.write(cr); + } + catch(Exception e){ + } + } + + public void writeSECSHPublicKey(String name, String comment) throws java.io.FileNotFoundException, java.io.IOException{ + FileOutputStream fos=new FileOutputStream(name); + writeSECSHPublicKey(fos, comment); + fos.close(); + } + + + public void writePrivateKey(String name) throws java.io.FileNotFoundException, java.io.IOException{ + FileOutputStream fos=new FileOutputStream(name); + writePrivateKey(fos); + fos.close(); + } + + public String getFingerPrint(){ + if(hash==null) hash=genHash(); + byte[] kblob=getPublicKeyBlob(); + if(kblob==null) return null; + return getKeySize()+" "+Util.getFingerPrint(hash, kblob); + } + + private byte[] encrypt(byte[] plain, byte[][] _iv){ + if(passphrase==null) return plain; + + if(cipher==null) cipher=genCipher(); + byte[] iv=_iv[0]=new byte[cipher.getIVSize()]; + + if(random==null) random=genRandom(); + random.fill(iv, 0, iv.length); + + byte[] key=genKey(passphrase, iv); + byte[] encoded=plain; + + // PKCS#5Padding + { + //int bsize=cipher.getBlockSize(); + int bsize=cipher.getIVSize(); + byte[] foo=new byte[(encoded.length/bsize+1)*bsize]; + System.arraycopy(encoded, 0, foo, 0, encoded.length); + int padding=bsize-encoded.length%bsize; + for(int i=foo.length-1; (foo.length-padding)<=i; i--){ + foo[i]=(byte)padding; + } + encoded=foo; + } + + try{ + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + cipher.update(encoded, 0, encoded.length, encoded, 0); + } + catch(Exception e){ + //System.err.println(e); + } + Util.bzero(key); + return encoded; + } + + abstract boolean parse(byte[] data); + + private byte[] decrypt(byte[] data, byte[] passphrase, byte[] iv){ + /* + if(iv==null){ // FSecure + iv=new byte[8]; + for(int i=0; i<iv.length; i++)iv[i]=0; + } + */ + try{ + byte[] key=genKey(passphrase, iv); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + Util.bzero(key); + byte[] plain=new byte[data.length]; + cipher.update(data, 0, data.length, plain, 0); + return plain; + } + catch(Exception e){ + //System.err.println(e); + } + return null; + } + + int writeSEQUENCE(byte[] buf, int index, int len){ + buf[index++]=0x30; + index=writeLength(buf, index, len); + return index; + } + int writeINTEGER(byte[] buf, int index, byte[] data){ + buf[index++]=0x02; + index=writeLength(buf, index, data.length); + System.arraycopy(data, 0, buf, index, data.length); + index+=data.length; + return index; + } + + int countLength(int len){ + int i=1; + if(len<=0x7f) return i; + while(len>0){ + len>>>=8; + i++; + } + return i; + } + + int writeLength(byte[] data, int index, int len){ + int i=countLength(len)-1; + if(i==0){ + data[index++]=(byte)len; + return index; + } + data[index++]=(byte)(0x80|i); + int j=index+i; + while(i>0){ + data[index+i-1]=(byte)(len&0xff); + len>>>=8; + i--; + } + return j; + } + + private Random genRandom(){ + if(random==null){ + try{ + Class c=Class.forName(jsch.getConfig("random")); + random=(Random)(c.newInstance()); + } + catch(Exception e){ System.err.println("connect: random "+e); } + } + return random; + } + + private HASH genHash(){ + try{ + Class c=Class.forName(jsch.getConfig("md5")); + hash=(HASH)(c.newInstance()); + hash.init(); + } + catch(Exception e){ + } + return hash; + } + private Cipher genCipher(){ + try{ + Class c; + c=Class.forName(jsch.getConfig("3des-cbc")); + cipher=(Cipher)(c.newInstance()); + } + catch(Exception e){ + } + return cipher; + } + + /* + hash is MD5 + h(0) <- hash(passphrase, iv); + h(n) <- hash(h(n-1), passphrase, iv); + key <- (h(0),...,h(n))[0,..,key.length]; + */ + synchronized byte[] genKey(byte[] passphrase, byte[] iv){ + if(cipher==null) cipher=genCipher(); + if(hash==null) hash=genHash(); + + byte[] key=new byte[cipher.getBlockSize()]; + int hsize=hash.getBlockSize(); + byte[] hn=new byte[key.length/hsize*hsize+ + (key.length%hsize==0?0:hsize)]; + try{ + byte[] tmp=null; + if(vendor==VENDOR_OPENSSH){ + for(int index=0; index+hsize<=hn.length;){ + if(tmp!=null){ hash.update(tmp, 0, tmp.length); } + hash.update(passphrase, 0, passphrase.length); + hash.update(iv, 0, iv.length > 8 ? 8: iv.length); + tmp=hash.digest(); + System.arraycopy(tmp, 0, hn, index, tmp.length); + index+=tmp.length; + } + System.arraycopy(hn, 0, key, 0, key.length); + } + else if(vendor==VENDOR_FSECURE){ + for(int index=0; index+hsize<=hn.length;){ + if(tmp!=null){ hash.update(tmp, 0, tmp.length); } + hash.update(passphrase, 0, passphrase.length); + tmp=hash.digest(); + System.arraycopy(tmp, 0, hn, index, tmp.length); + index+=tmp.length; + } + System.arraycopy(hn, 0, key, 0, key.length); + } + } + catch(Exception e){ + System.err.println(e); + } + return key; + } + + public void setPassphrase(String passphrase){ + if(passphrase==null || passphrase.length()==0){ + setPassphrase((byte[])null); + } + else{ + setPassphrase(Util.str2byte(passphrase)); + } + } + public void setPassphrase(byte[] passphrase){ + if(passphrase!=null && passphrase.length==0) + passphrase=null; + this.passphrase=passphrase; + } + + private boolean encrypted=false; + private byte[] data=null; + private byte[] iv=null; + private byte[] publickeyblob=null; + + public boolean isEncrypted(){ return encrypted; } + public boolean decrypt(String _passphrase){ + if(_passphrase==null || _passphrase.length()==0){ + return !encrypted; + } + return decrypt(Util.str2byte(_passphrase)); + } + public boolean decrypt(byte[] _passphrase){ + if(!encrypted){ + return true; + } + if(_passphrase==null){ + return !encrypted; + } + byte[] bar=new byte[_passphrase.length]; + System.arraycopy(_passphrase, 0, bar, 0, bar.length); + _passphrase=bar; + byte[] foo=decrypt(data, _passphrase, iv); + Util.bzero(_passphrase); + if(parse(foo)){ + encrypted=false; + } + return !encrypted; + } + + public static KeyPair load(JSch jsch, String prvkey) throws JSchException{ + String pubkey=prvkey+".pub"; + if(!new File(pubkey).exists()){ + pubkey=null; + } + return load(jsch, prvkey, pubkey); + } + public static KeyPair load(JSch jsch, String prvkey, String pubkey) throws JSchException{ + + byte[] iv=new byte[8]; // 8 + boolean encrypted=true; + byte[] data=null; + + byte[] publickeyblob=null; + + int type=ERROR; + int vendor=VENDOR_OPENSSH; + String publicKeyComment = ""; + Cipher cipher=null; + + try{ + File file=new File(prvkey); + FileInputStream fis=new FileInputStream(prvkey); + byte[] buf=new byte[(int)(file.length())]; + int len=0; + while(true){ + int i=fis.read(buf, len, buf.length-len); + if(i<=0) + break; + len+=i; + } + fis.close(); + + int i=0; + + while(i<len){ + if(buf[i] == '-' && i+4<len && + buf[i+1] == '-' && buf[i+2] == '-' && + buf[i+3] == '-' && buf[i+4] == '-'){ + break; + } + i++; + } + + while(i<len){ + if(buf[i]=='B'&& i+3<len && buf[i+1]=='E'&& buf[i+2]=='G'&& buf[i+3]=='I'){ + i+=6; + if(buf[i]=='D'&& buf[i+1]=='S'&& buf[i+2]=='A'){ type=DSA; } + else if(buf[i]=='R'&& buf[i+1]=='S'&& buf[i+2]=='A'){ type=RSA; } + else if(buf[i]=='S'&& buf[i+1]=='S'&& buf[i+2]=='H'){ // FSecure + type=UNKNOWN; + vendor=VENDOR_FSECURE; + } + else{ + throw new JSchException("invalid privatekey: "+prvkey); + } + i+=3; + continue; + } + if(buf[i]=='A'&& i+7<len && buf[i+1]=='E'&& buf[i+2]=='S'&& buf[i+3]=='-' && + buf[i+4]=='2'&& buf[i+5]=='5'&& buf[i+6]=='6'&& buf[i+7]=='-'){ + i+=8; + if(Session.checkCipher((String)jsch.getConfig("aes256-cbc"))){ + Class c=Class.forName((String)jsch.getConfig("aes256-cbc")); + cipher=(Cipher)(c.newInstance()); + // key=new byte[cipher.getBlockSize()]; + iv=new byte[cipher.getIVSize()]; + } + else{ + throw new JSchException("privatekey: aes256-cbc is not available "+prvkey); + } + continue; + } + if(buf[i]=='A'&& i+7<len && buf[i+1]=='E'&& buf[i+2]=='S'&& buf[i+3]=='-' && + buf[i+4]=='1'&& buf[i+5]=='9'&& buf[i+6]=='2'&& buf[i+7]=='-'){ + i+=8; + if(Session.checkCipher((String)jsch.getConfig("aes192-cbc"))){ + Class c=Class.forName((String)jsch.getConfig("aes192-cbc")); + cipher=(Cipher)(c.newInstance()); + // key=new byte[cipher.getBlockSize()]; + iv=new byte[cipher.getIVSize()]; + } + else{ + throw new JSchException("privatekey: aes192-cbc is not available "+prvkey); + } + continue; + } + if(buf[i]=='A'&& i+7<len && buf[i+1]=='E'&& buf[i+2]=='S'&& buf[i+3]=='-' && + buf[i+4]=='1'&& buf[i+5]=='2'&& buf[i+6]=='8'&& buf[i+7]=='-'){ + i+=8; + if(Session.checkCipher((String)jsch.getConfig("aes128-cbc"))){ + Class c=Class.forName((String)jsch.getConfig("aes128-cbc")); + cipher=(Cipher)(c.newInstance()); + // key=new byte[cipher.getBlockSize()]; + iv=new byte[cipher.getIVSize()]; + } + else{ + throw new JSchException("privatekey: aes128-cbc is not available "+prvkey); + } + continue; + } + if(buf[i]=='C'&& i+3<len && buf[i+1]=='B'&& buf[i+2]=='C'&& buf[i+3]==','){ + i+=4; + for(int ii=0; ii<iv.length; ii++){ + iv[ii]=(byte)(((a2b(buf[i++])<<4)&0xf0)+(a2b(buf[i++])&0xf)); + } + continue; + } + if(buf[i]==0x0d && i+1<buf.length && buf[i+1]==0x0a){ + i++; + continue; + } + if(buf[i]==0x0a && i+1<buf.length){ + if(buf[i+1]==0x0a){ i+=2; break; } + if(buf[i+1]==0x0d && + i+2<buf.length && buf[i+2]==0x0a){ + i+=3; break; + } + boolean inheader=false; + for(int j=i+1; j<buf.length; j++){ + if(buf[j]==0x0a) break; + //if(buf[j]==0x0d) break; + if(buf[j]==':'){inheader=true; break;} + } + if(!inheader){ + i++; + encrypted=false; // no passphrase + break; + } + } + i++; + } + + if(type==ERROR){ + throw new JSchException("invalid privatekey: "+prvkey); + } + + int start=i; + while(i<len){ + if(buf[i]==0x0a){ + boolean xd=(buf[i-1]==0x0d); + System.arraycopy(buf, i+1, + buf, + i-(xd ? 1 : 0), + len-i-1-(xd ? 1 : 0) + ); + if(xd)len--; + len--; + continue; + } + if(buf[i]=='-'){ break; } + i++; + } + data=Util.fromBase64(buf, start, i-start); + + if(data.length>4 && // FSecure + data[0]==(byte)0x3f && + data[1]==(byte)0x6f && + data[2]==(byte)0xf9 && + data[3]==(byte)0xeb){ + + Buffer _buf=new Buffer(data); + _buf.getInt(); // 0x3f6ff9be + _buf.getInt(); + byte[]_type=_buf.getString(); + //System.err.println("type: "+new String(_type)); + String _cipher=Util.byte2str(_buf.getString()); + //System.err.println("cipher: "+_cipher); + if(_cipher.equals("3des-cbc")){ + _buf.getInt(); + byte[] foo=new byte[data.length-_buf.getOffSet()]; + _buf.getByte(foo); + data=foo; + encrypted=true; + throw new JSchException("unknown privatekey format: "+prvkey); + } + else if(_cipher.equals("none")){ + _buf.getInt(); + _buf.getInt(); + + encrypted=false; + + byte[] foo=new byte[data.length-_buf.getOffSet()]; + _buf.getByte(foo); + data=foo; + } + } + + if(pubkey!=null){ + try{ + file=new File(pubkey); + fis=new FileInputStream(pubkey); + buf=new byte[(int)(file.length())]; + len=0; + while(true){ + i=fis.read(buf, len, buf.length-len); + if(i<=0) + break; + len+=i; + } + fis.close(); + + if(buf.length>4 && // FSecure's public key + buf[0]=='-' && buf[1]=='-' && buf[2]=='-' && buf[3]=='-'){ + + boolean valid=true; + i=0; + do{i++;}while(buf.length>i && buf[i]!=0x0a); + if(buf.length<=i) {valid=false;} + + while(valid){ + if(buf[i]==0x0a){ + boolean inheader=false; + for(int j=i+1; j<buf.length; j++){ + if(buf[j]==0x0a) break; + if(buf[j]==':'){inheader=true; break;} + } + if(!inheader){ + i++; + break; + } + } + i++; + } + if(buf.length<=i){valid=false;} + + start=i; + while(valid && i<len){ + if(buf[i]==0x0a){ + System.arraycopy(buf, i+1, buf, i, len-i-1); + len--; + continue; + } + if(buf[i]=='-'){ break; } + i++; + } + if(valid){ + publickeyblob=Util.fromBase64(buf, start, i-start); + if(type==UNKNOWN){ + if(publickeyblob[8]=='d'){ type=DSA; } + else if(publickeyblob[8]=='r'){ type=RSA; } + } + } + } + else{ + if(buf[0]=='s'&& buf[1]=='s'&& buf[2]=='h' && buf[3]=='-'){ + i=0; + while(i<len){ if(buf[i]==' ')break; i++;} i++; + if(i<len){ + start=i; + while(i<len){ if(buf[i]==' ')break; i++;} + publickeyblob=Util.fromBase64(buf, start, i-start); + } + if(i++<len){ + int s=i; + while(i<len){ if(buf[i]=='\n')break; i++;} + if(i<len){ + publicKeyComment = new String(buf, s, i-s); + } + } + } + } + } + catch(Exception ee){ + } + } + } + catch(Exception e){ + if(e instanceof JSchException) throw (JSchException)e; + if(e instanceof Throwable) + throw new JSchException(e.toString(), (Throwable)e); + throw new JSchException(e.toString()); + } + + KeyPair kpair=null; + if(type==DSA){ kpair=new KeyPairDSA(jsch); } + else if(type==RSA){ kpair=new KeyPairRSA(jsch); } + + if(kpair!=null){ + kpair.encrypted=encrypted; + kpair.publickeyblob=publickeyblob; + kpair.vendor=vendor; + kpair.publicKeyComment=publicKeyComment; + kpair.cipher=cipher; + + if(encrypted){ + kpair.iv=iv; + kpair.data=data; + } + else{ + if(kpair.parse(data)){ + return kpair; + } + else{ + throw new JSchException("invalid privatekey: "+prvkey); + } + } + } + + return kpair; + } + + static private byte a2b(byte c){ + if('0'<=c&&c<='9') return (byte)(c-'0'); + return (byte)(c-'a'+10); + } + static private byte b2a(byte c){ + if(0<=c&&c<=9) return (byte)(c+'0'); + return (byte)(c-10+'A'); + } + + public void dispose(){ + Util.bzero(passphrase); + } + + public void finalize (){ + dispose(); + } +} |