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.FileOutputStream;
033: import java.io.FileInputStream;
034: import java.io.File;
035:
036: public abstract class KeyPair {
037: public static final int ERROR = 0;
038: public static final int DSA = 1;
039: public static final int RSA = 2;
040: public static final int UNKNOWN = 3;
041:
042: static final int VENDOR_OPENSSH = 0;
043: static final int VENDOR_FSECURE = 1;
044: int vendor = VENDOR_OPENSSH;
045:
046: private static final byte[] cr = "\n".getBytes();
047:
048: public static KeyPair genKeyPair(JSch jsch, int type)
049: throws JSchException {
050: return genKeyPair(jsch, type, 1024);
051: }
052:
053: public static KeyPair genKeyPair(JSch jsch, int type, int key_size)
054: throws JSchException {
055: KeyPair kpair = null;
056: if (type == DSA) {
057: kpair = new KeyPairDSA(jsch);
058: } else if (type == RSA) {
059: kpair = new KeyPairRSA(jsch);
060: }
061: if (kpair != null) {
062: kpair.generate(key_size);
063: }
064: return kpair;
065: }
066:
067: abstract void generate(int key_size) throws JSchException;
068:
069: abstract byte[] getBegin();
070:
071: abstract byte[] getEnd();
072:
073: abstract int getKeySize();
074:
075: JSch jsch = null;
076: private Cipher cipher;
077: private HASH hash;
078: private Random random;
079:
080: private byte[] passphrase;
081:
082: public KeyPair(JSch jsch) {
083: this .jsch = jsch;
084: }
085:
086: static byte[][] header = { "Proc-Type: 4,ENCRYPTED".getBytes(),
087: "DEK-Info: DES-EDE3-CBC,".getBytes() };
088:
089: abstract byte[] getPrivateKey();
090:
091: public void writePrivateKey(java.io.OutputStream out) {
092: byte[] plain = getPrivateKey();
093: byte[][] _iv = new byte[1][];
094: byte[] encoded = encrypt(plain, _iv);
095: if (encoded != plain)
096: Util.bzero(plain);
097: byte[] iv = _iv[0];
098: byte[] prv = Util.toBase64(encoded, 0, encoded.length);
099:
100: try {
101: out.write(getBegin());
102: out.write(cr);
103: if (passphrase != null) {
104: out.write(header[0]);
105: out.write(cr);
106: out.write(header[1]);
107: for (int i = 0; i < iv.length; i++) {
108: out.write(b2a((byte) ((iv[i] >>> 4) & 0x0f)));
109: out.write(b2a((byte) (iv[i] & 0x0f)));
110: }
111: out.write(cr);
112: out.write(cr);
113: }
114: int i = 0;
115: while (i < prv.length) {
116: if (i + 64 < prv.length) {
117: out.write(prv, i, 64);
118: out.write(cr);
119: i += 64;
120: continue;
121: }
122: out.write(prv, i, prv.length - i);
123: out.write(cr);
124: break;
125: }
126: out.write(getEnd());
127: out.write(cr);
128: //out.close();
129: } catch (Exception e) {
130: }
131: }
132:
133: private static byte[] space = " ".getBytes();
134:
135: abstract byte[] getKeyTypeName();
136:
137: public abstract int getKeyType();
138:
139: public byte[] getPublicKeyBlob() {
140: return publickeyblob;
141: }
142:
143: public void writePublicKey(java.io.OutputStream out, String comment) {
144: byte[] pubblob = getPublicKeyBlob();
145: byte[] pub = Util.toBase64(pubblob, 0, pubblob.length);
146: try {
147: out.write(getKeyTypeName());
148: out.write(space);
149: out.write(pub, 0, pub.length);
150: out.write(space);
151: out.write(comment.getBytes());
152: out.write(cr);
153: } catch (Exception e) {
154: }
155: }
156:
157: public void writePublicKey(String name, String comment)
158: throws java.io.FileNotFoundException, java.io.IOException {
159: FileOutputStream fos = new FileOutputStream(name);
160: writePublicKey(fos, comment);
161: fos.close();
162: }
163:
164: public void writeSECSHPublicKey(java.io.OutputStream out,
165: String comment) {
166: byte[] pubblob = getPublicKeyBlob();
167: byte[] pub = Util.toBase64(pubblob, 0, pubblob.length);
168: try {
169: out.write("---- BEGIN SSH2 PUBLIC KEY ----".getBytes());
170: out.write(cr);
171: out.write(("Comment: \"" + comment + "\"").getBytes());
172: out.write(cr);
173: int index = 0;
174: while (index < pub.length) {
175: int len = 70;
176: if ((pub.length - index) < len)
177: len = pub.length - index;
178: out.write(pub, index, len);
179: out.write(cr);
180: index += len;
181: }
182: out.write("---- END SSH2 PUBLIC KEY ----".getBytes());
183: out.write(cr);
184: } catch (Exception e) {
185: }
186: }
187:
188: public void writeSECSHPublicKey(String name, String comment)
189: throws java.io.FileNotFoundException, java.io.IOException {
190: FileOutputStream fos = new FileOutputStream(name);
191: writeSECSHPublicKey(fos, comment);
192: fos.close();
193: }
194:
195: public void writePrivateKey(String name)
196: throws java.io.FileNotFoundException, java.io.IOException {
197: FileOutputStream fos = new FileOutputStream(name);
198: writePrivateKey(fos);
199: fos.close();
200: }
201:
202: public String getFingerPrint() {
203: if (hash == null)
204: hash = genHash();
205: byte[] kblob = getPublicKeyBlob();
206: if (kblob == null)
207: return null;
208: return getKeySize() + " " + Util.getFingerPrint(hash, kblob);
209: }
210:
211: private byte[] encrypt(byte[] plain, byte[][] _iv) {
212: if (passphrase == null)
213: return plain;
214:
215: if (cipher == null)
216: cipher = genCipher();
217: byte[] iv = _iv[0] = new byte[cipher.getIVSize()];
218:
219: if (random == null)
220: random = genRandom();
221: random.fill(iv, 0, iv.length);
222:
223: byte[] key = genKey(passphrase, iv);
224: byte[] encoded = plain;
225:
226: // PKCS#5Padding
227: {
228: //int bsize=cipher.getBlockSize();
229: int bsize = cipher.getIVSize();
230: byte[] foo = new byte[(encoded.length / bsize + 1) * bsize];
231: System.arraycopy(encoded, 0, foo, 0, encoded.length);
232: int padding = bsize - encoded.length % bsize;
233: for (int i = foo.length - 1; (foo.length - padding) <= i; i--) {
234: foo[i] = (byte) padding;
235: }
236: encoded = foo;
237: }
238:
239: try {
240: cipher.init(Cipher.ENCRYPT_MODE, key, iv);
241: cipher.update(encoded, 0, encoded.length, encoded, 0);
242: } catch (Exception e) {
243: //System.err.println(e);
244: }
245: Util.bzero(key);
246: return encoded;
247: }
248:
249: abstract boolean parse(byte[] data);
250:
251: private byte[] decrypt(byte[] data, byte[] passphrase, byte[] iv) {
252: /*
253: if(iv==null){ // FSecure
254: iv=new byte[8];
255: for(int i=0; i<iv.length; i++)iv[i]=0;
256: }
257: */
258: try {
259: byte[] key = genKey(passphrase, iv);
260: cipher.init(Cipher.DECRYPT_MODE, key, iv);
261: Util.bzero(key);
262: byte[] plain = new byte[data.length];
263: cipher.update(data, 0, data.length, plain, 0);
264: return plain;
265: } catch (Exception e) {
266: //System.err.println(e);
267: }
268: return null;
269: }
270:
271: int writeSEQUENCE(byte[] buf, int index, int len) {
272: buf[index++] = 0x30;
273: index = writeLength(buf, index, len);
274: return index;
275: }
276:
277: int writeINTEGER(byte[] buf, int index, byte[] data) {
278: buf[index++] = 0x02;
279: index = writeLength(buf, index, data.length);
280: System.arraycopy(data, 0, buf, index, data.length);
281: index += data.length;
282: return index;
283: }
284:
285: int countLength(int len) {
286: int i = 1;
287: if (len <= 0x7f)
288: return i;
289: while (len > 0) {
290: len >>>= 8;
291: i++;
292: }
293: return i;
294: }
295:
296: int writeLength(byte[] data, int index, int len) {
297: int i = countLength(len) - 1;
298: if (i == 0) {
299: data[index++] = (byte) len;
300: return index;
301: }
302: data[index++] = (byte) (0x80 | i);
303: int j = index + i;
304: while (i > 0) {
305: data[index + i - 1] = (byte) (len & 0xff);
306: len >>>= 8;
307: i--;
308: }
309: return j;
310: }
311:
312: private Random genRandom() {
313: if (random == null) {
314: try {
315: Class c = Class.forName(jsch.getConfig("random"));
316: random = (Random) (c.newInstance());
317: } catch (Exception e) {
318: System.err.println("connect: random " + e);
319: }
320: }
321: return random;
322: }
323:
324: private HASH genHash() {
325: try {
326: Class c = Class.forName(jsch.getConfig("md5"));
327: hash = (HASH) (c.newInstance());
328: hash.init();
329: } catch (Exception e) {
330: }
331: return hash;
332: }
333:
334: private Cipher genCipher() {
335: try {
336: Class c;
337: c = Class.forName(jsch.getConfig("3des-cbc"));
338: cipher = (Cipher) (c.newInstance());
339: } catch (Exception e) {
340: }
341: return cipher;
342: }
343:
344: /*
345: hash is MD5
346: h(0) <- hash(passphrase, iv);
347: h(n) <- hash(h(n-1), passphrase, iv);
348: key <- (h(0),...,h(n))[0,..,key.length];
349: */
350: synchronized byte[] genKey(byte[] passphrase, byte[] iv) {
351: if (cipher == null)
352: cipher = genCipher();
353: if (hash == null)
354: hash = genHash();
355:
356: byte[] key = new byte[cipher.getBlockSize()];
357: int hsize = hash.getBlockSize();
358: byte[] hn = new byte[key.length / hsize * hsize
359: + (key.length % hsize == 0 ? 0 : hsize)];
360: try {
361: byte[] tmp = null;
362: if (vendor == VENDOR_OPENSSH) {
363: for (int index = 0; index + hsize <= hn.length;) {
364: if (tmp != null) {
365: hash.update(tmp, 0, tmp.length);
366: }
367: hash.update(passphrase, 0, passphrase.length);
368: hash.update(iv, 0, iv.length);
369: tmp = hash.digest();
370: System.arraycopy(tmp, 0, hn, index, tmp.length);
371: index += tmp.length;
372: }
373: System.arraycopy(hn, 0, key, 0, key.length);
374: } else if (vendor == VENDOR_FSECURE) {
375: for (int index = 0; index + hsize <= hn.length;) {
376: if (tmp != null) {
377: hash.update(tmp, 0, tmp.length);
378: }
379: hash.update(passphrase, 0, passphrase.length);
380: tmp = hash.digest();
381: System.arraycopy(tmp, 0, hn, index, tmp.length);
382: index += tmp.length;
383: }
384: System.arraycopy(hn, 0, key, 0, key.length);
385: }
386: } catch (Exception e) {
387: System.err.println(e);
388: }
389: return key;
390: }
391:
392: public void setPassphrase(String passphrase) {
393: if (passphrase == null || passphrase.length() == 0) {
394: setPassphrase((byte[]) null);
395: } else {
396: setPassphrase(Util.str2byte(passphrase));
397: }
398: }
399:
400: public void setPassphrase(byte[] passphrase) {
401: if (passphrase != null && passphrase.length == 0)
402: passphrase = null;
403: this .passphrase = passphrase;
404: }
405:
406: private boolean encrypted = false;
407: private byte[] data = null;
408: private byte[] iv = null;
409: private byte[] publickeyblob = null;
410:
411: public boolean isEncrypted() {
412: return encrypted;
413: }
414:
415: public boolean decrypt(String _passphrase) {
416: if (_passphrase == null || _passphrase.length() == 0) {
417: return !encrypted;
418: }
419: return decrypt(Util.str2byte(_passphrase));
420: }
421:
422: public boolean decrypt(byte[] _passphrase) {
423: if (!encrypted) {
424: return true;
425: }
426: if (_passphrase == null) {
427: return !encrypted;
428: }
429: byte[] bar = new byte[_passphrase.length];
430: System.arraycopy(_passphrase, 0, bar, 0, bar.length);
431: _passphrase = bar;
432: byte[] foo = decrypt(data, _passphrase, iv);
433: Util.bzero(_passphrase);
434: if (parse(foo)) {
435: encrypted = false;
436: }
437: return !encrypted;
438: }
439:
440: public static KeyPair load(JSch jsch, String prvkey)
441: throws JSchException {
442: String pubkey = prvkey + ".pub";
443: if (!new File(pubkey).exists()) {
444: pubkey = null;
445: }
446: return load(jsch, prvkey, pubkey);
447: }
448:
449: public static KeyPair load(JSch jsch, String prvkey, String pubkey)
450: throws JSchException {
451:
452: byte[] iv = new byte[8]; // 8
453: boolean encrypted = true;
454: byte[] data = null;
455:
456: byte[] publickeyblob = null;
457:
458: int type = ERROR;
459: int vendor = VENDOR_OPENSSH;
460:
461: try {
462: File file = new File(prvkey);
463: FileInputStream fis = new FileInputStream(prvkey);
464: byte[] buf = new byte[(int) (file.length())];
465: int len = 0;
466: while (true) {
467: int i = fis.read(buf, len, buf.length - len);
468: if (i <= 0)
469: break;
470: len += i;
471: }
472: fis.close();
473:
474: int i = 0;
475:
476: while (i < len) {
477: if (buf[i] == 'B' && buf[i + 1] == 'E'
478: && buf[i + 2] == 'G' && buf[i + 3] == 'I') {
479: i += 6;
480: if (buf[i] == 'D' && buf[i + 1] == 'S'
481: && buf[i + 2] == 'A') {
482: type = DSA;
483: } else if (buf[i] == 'R' && buf[i + 1] == 'S'
484: && buf[i + 2] == 'A') {
485: type = RSA;
486: } else if (buf[i] == 'S' && buf[i + 1] == 'S'
487: && buf[i + 2] == 'H') { // FSecure
488: type = UNKNOWN;
489: vendor = VENDOR_FSECURE;
490: } else {
491: //System.err.println("invalid format: "+identity);
492: throw new JSchException("invalid privatekey: "
493: + prvkey);
494: }
495: i += 3;
496: continue;
497: }
498: if (buf[i] == 'C' && buf[i + 1] == 'B'
499: && buf[i + 2] == 'C' && buf[i + 3] == ',') {
500: i += 4;
501: for (int ii = 0; ii < iv.length; ii++) {
502: iv[ii] = (byte) (((a2b(buf[i++]) << 4) & 0xf0) + (a2b(buf[i++]) & 0xf));
503: }
504: continue;
505: }
506: if (buf[i] == 0x0d && i + 1 < buf.length
507: && buf[i + 1] == 0x0a) {
508: i++;
509: continue;
510: }
511: if (buf[i] == 0x0a && i + 1 < buf.length) {
512: if (buf[i + 1] == 0x0a) {
513: i += 2;
514: break;
515: }
516: if (buf[i + 1] == 0x0d && i + 2 < buf.length
517: && buf[i + 2] == 0x0a) {
518: i += 3;
519: break;
520: }
521: boolean inheader = false;
522: for (int j = i + 1; j < buf.length; j++) {
523: if (buf[j] == 0x0a)
524: break;
525: //if(buf[j]==0x0d) break;
526: if (buf[j] == ':') {
527: inheader = true;
528: break;
529: }
530: }
531: if (!inheader) {
532: i++;
533: encrypted = false; // no passphrase
534: break;
535: }
536: }
537: i++;
538: }
539:
540: if (type == ERROR) {
541: throw new JSchException("invalid privatekey: " + prvkey);
542: }
543:
544: int start = i;
545: while (i < len) {
546: if (buf[i] == 0x0a) {
547: boolean xd = (buf[i - 1] == 0x0d);
548: System.arraycopy(buf, i + 1, buf, i - (xd ? 1 : 0),
549: len - i - 1 - (xd ? 1 : 0));
550: if (xd)
551: len--;
552: len--;
553: continue;
554: }
555: if (buf[i] == '-') {
556: break;
557: }
558: i++;
559: }
560: data = Util.fromBase64(buf, start, i - start);
561:
562: if (data.length > 4
563: && // FSecure
564: data[0] == (byte) 0x3f && data[1] == (byte) 0x6f
565: && data[2] == (byte) 0xf9 && data[3] == (byte) 0xeb) {
566:
567: Buffer _buf = new Buffer(data);
568: _buf.getInt(); // 0x3f6ff9be
569: _buf.getInt();
570: byte[] _type = _buf.getString();
571: //System.err.println("type: "+new String(_type));
572: byte[] _cipher = _buf.getString();
573: String cipher = new String(_cipher);
574: //System.err.println("cipher: "+cipher);
575: if (cipher.equals("3des-cbc")) {
576: _buf.getInt();
577: byte[] foo = new byte[data.length
578: - _buf.getOffSet()];
579: _buf.getByte(foo);
580: data = foo;
581: encrypted = true;
582: throw new JSchException(
583: "unknown privatekey format: " + prvkey);
584: } else if (cipher.equals("none")) {
585: _buf.getInt();
586: _buf.getInt();
587:
588: encrypted = false;
589:
590: byte[] foo = new byte[data.length
591: - _buf.getOffSet()];
592: _buf.getByte(foo);
593: data = foo;
594: }
595: }
596:
597: if (pubkey != null) {
598: try {
599: file = new File(pubkey);
600: fis = new FileInputStream(pubkey);
601: buf = new byte[(int) (file.length())];
602: len = 0;
603: while (true) {
604: i = fis.read(buf, len, buf.length - len);
605: if (i <= 0)
606: break;
607: len += i;
608: }
609: fis.close();
610:
611: if (buf.length > 4
612: && // FSecure's public key
613: buf[0] == '-' && buf[1] == '-'
614: && buf[2] == '-' && buf[3] == '-') {
615:
616: boolean valid = true;
617: i = 0;
618: do {
619: i++;
620: } while (buf.length > i && buf[i] != 0x0a);
621: if (buf.length <= i) {
622: valid = false;
623: }
624:
625: while (valid) {
626: if (buf[i] == 0x0a) {
627: boolean inheader = false;
628: for (int j = i + 1; j < buf.length; j++) {
629: if (buf[j] == 0x0a)
630: break;
631: if (buf[j] == ':') {
632: inheader = true;
633: break;
634: }
635: }
636: if (!inheader) {
637: i++;
638: break;
639: }
640: }
641: i++;
642: }
643: if (buf.length <= i) {
644: valid = false;
645: }
646:
647: start = i;
648: while (valid && i < len) {
649: if (buf[i] == 0x0a) {
650: System.arraycopy(buf, i + 1, buf, i,
651: len - i - 1);
652: len--;
653: continue;
654: }
655: if (buf[i] == '-') {
656: break;
657: }
658: i++;
659: }
660: if (valid) {
661: publickeyblob = Util.fromBase64(buf, start,
662: i - start);
663: if (type == UNKNOWN) {
664: if (publickeyblob[8] == 'd') {
665: type = DSA;
666: } else if (publickeyblob[8] == 'r') {
667: type = RSA;
668: }
669: }
670: }
671: } else {
672: if (buf[0] == 's' && buf[1] == 's'
673: && buf[2] == 'h' && buf[3] == '-') {
674: i = 0;
675: while (i < len) {
676: if (buf[i] == ' ')
677: break;
678: i++;
679: }
680: i++;
681: if (i < len) {
682: start = i;
683: while (i < len) {
684: if (buf[i] == ' ')
685: break;
686: i++;
687: }
688: publickeyblob = Util.fromBase64(buf,
689: start, i - start);
690: }
691: }
692: }
693: } catch (Exception ee) {
694: }
695: }
696: } catch (Exception e) {
697: if (e instanceof JSchException)
698: throw (JSchException) e;
699: if (e instanceof Throwable)
700: throw new JSchException(e.toString(), (Throwable) e);
701: throw new JSchException(e.toString());
702: }
703:
704: KeyPair kpair = null;
705: if (type == DSA) {
706: kpair = new KeyPairDSA(jsch);
707: } else if (type == RSA) {
708: kpair = new KeyPairRSA(jsch);
709: }
710:
711: if (kpair != null) {
712: kpair.encrypted = encrypted;
713: kpair.publickeyblob = publickeyblob;
714: kpair.vendor = vendor;
715:
716: if (encrypted) {
717: kpair.iv = iv;
718: kpair.data = data;
719: } else {
720: if (kpair.parse(data)) {
721: return kpair;
722: } else {
723: throw new JSchException("invalid privatekey: "
724: + prvkey);
725: }
726: }
727: }
728:
729: return kpair;
730: }
731:
732: static private byte a2b(byte c) {
733: if ('0' <= c && c <= '9')
734: return (byte) (c - '0');
735: return (byte) (c - 'a' + 10);
736: }
737:
738: static private byte b2a(byte c) {
739: if (0 <= c && c <= 9)
740: return (byte) (c + '0');
741: return (byte) (c - 10 + 'A');
742: }
743:
744: public void dispose() {
745: Util.bzero(passphrase);
746: }
747:
748: public void finalize() {
749: dispose();
750: }
751: }
|