001: package ch.ethz.ssh2.crypto;
002:
003: import java.io.BufferedReader;
004: import java.io.CharArrayReader;
005: import java.io.IOException;
006: import java.math.BigInteger;
007:
008: import ch.ethz.ssh2.crypto.cipher.AES;
009: import ch.ethz.ssh2.crypto.cipher.BlockCipher;
010: import ch.ethz.ssh2.crypto.cipher.CBCMode;
011: import ch.ethz.ssh2.crypto.cipher.DES;
012: import ch.ethz.ssh2.crypto.cipher.DESede;
013: import ch.ethz.ssh2.crypto.digest.MD5;
014: import ch.ethz.ssh2.signature.DSAPrivateKey;
015: import ch.ethz.ssh2.signature.RSAPrivateKey;
016:
017: /**
018: * PEM Support.
019: *
020: * @author Christian Plattner, plattner@inf.ethz.ch
021: * @version $Id: PEMDecoder.java,v 1.7 2006/02/02 09:11:03 cplattne Exp $
022: */
023: public class PEMDecoder {
024: private static final int PEM_RSA_PRIVATE_KEY = 1;
025: private static final int PEM_DSA_PRIVATE_KEY = 2;
026:
027: private static final int hexToInt(char c) {
028: if ((c >= 'a') && (c <= 'f')) {
029: return (c - 'a') + 10;
030: }
031:
032: if ((c >= 'A') && (c <= 'F')) {
033: return (c - 'A') + 10;
034: }
035:
036: if ((c >= '0') && (c <= '9')) {
037: return (c - '0');
038: }
039:
040: throw new IllegalArgumentException("Need hex char");
041: }
042:
043: private static byte[] hexToByteArray(String hex) {
044: if (hex == null)
045: throw new IllegalArgumentException("null argument");
046:
047: if ((hex.length() % 2) != 0)
048: throw new IllegalArgumentException(
049: "Uneven string length in hex encoding.");
050:
051: byte decoded[] = new byte[hex.length() / 2];
052:
053: for (int i = 0; i < decoded.length; i++) {
054: int hi = hexToInt(hex.charAt(i * 2));
055: int lo = hexToInt(hex.charAt((i * 2) + 1));
056:
057: decoded[i] = (byte) (hi * 16 + lo);
058: }
059:
060: return decoded;
061: }
062:
063: private static byte[] generateKeyFromPasswordSaltWithMD5(
064: byte[] password, byte[] salt, int keyLen)
065: throws IOException {
066: if (salt.length < 8)
067: throw new IllegalArgumentException(
068: "Salt needs to be at least 8 bytes for key generation.");
069:
070: MD5 md5 = new MD5();
071:
072: byte[] key = new byte[keyLen];
073: byte[] tmp = new byte[md5.getDigestLength()];
074:
075: while (true) {
076: md5.update(password, 0, password.length);
077: md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the salt in this step.
078: // This took me two hours until I got AES-xxx running.
079:
080: int copy = (keyLen < tmp.length) ? keyLen : tmp.length;
081:
082: md5.digest(tmp, 0);
083:
084: System.arraycopy(tmp, 0, key, key.length - keyLen, copy);
085:
086: keyLen -= copy;
087:
088: if (keyLen == 0)
089: return key;
090:
091: md5.update(tmp, 0, tmp.length);
092: }
093: }
094:
095: private static byte[] removePadding(byte[] buff, int blockSize)
096: throws IOException {
097: /* Removes RFC 1423/PKCS #7 padding */
098:
099: int rfc_1423_padding = buff[buff.length - 1] & 0xff;
100:
101: if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize))
102: throw new IOException(
103: "Decrypted PEM has wrong padding, did you specify the correct password?");
104:
105: for (int i = 2; i <= rfc_1423_padding; i++) {
106: if (buff[buff.length - i] != rfc_1423_padding)
107: throw new IOException(
108: "Decrypted PEM has wrong padding, did you specify the correct password?");
109: }
110:
111: byte[] tmp = new byte[buff.length - rfc_1423_padding];
112: System.arraycopy(buff, 0, tmp, 0, buff.length
113: - rfc_1423_padding);
114: return tmp;
115: }
116:
117: private static final PEMStructure parsePEM(char[] pem)
118: throws IOException {
119: PEMStructure ps = new PEMStructure();
120:
121: String line = null;
122:
123: BufferedReader br = new BufferedReader(new CharArrayReader(pem));
124:
125: String endLine = null;
126:
127: while (true) {
128: line = br.readLine();
129:
130: if (line == null)
131: throw new IOException(
132: "Invalid PEM structure, '-----BEGIN...' missing");
133:
134: line = line.trim();
135:
136: if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) {
137: endLine = "-----END DSA PRIVATE KEY-----";
138: ps.pemType = PEM_DSA_PRIVATE_KEY;
139: break;
140: }
141:
142: if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) {
143: endLine = "-----END RSA PRIVATE KEY-----";
144: ps.pemType = PEM_RSA_PRIVATE_KEY;
145: break;
146: }
147: }
148:
149: while (true) {
150: line = br.readLine();
151:
152: if (line == null)
153: throw new IOException("Invalid PEM structure, "
154: + endLine + " missing");
155:
156: line = line.trim();
157:
158: int sem_idx = line.indexOf(':');
159:
160: if (sem_idx == -1)
161: break;
162:
163: String name = line.substring(0, sem_idx + 1);
164: String value = line.substring(sem_idx + 1);
165:
166: String values[] = value.split(",");
167:
168: for (int i = 0; i < values.length; i++)
169: values[i] = values[i].trim();
170:
171: // Proc-Type: 4,ENCRYPTED
172: // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483
173:
174: if ("Proc-Type:".equals(name)) {
175: ps.procType = values;
176: continue;
177: }
178:
179: if ("DEK-Info:".equals(name)) {
180: ps.dekInfo = values;
181: continue;
182: }
183: /* Ignore line */
184: }
185:
186: StringBuffer keyData = new StringBuffer();
187:
188: while (true) {
189: if (line == null)
190: throw new IOException("Invalid PEM structure, "
191: + endLine + " missing");
192:
193: line = line.trim();
194:
195: if (line.startsWith(endLine))
196: break;
197:
198: keyData.append(line);
199:
200: line = br.readLine();
201: }
202:
203: char[] pem_chars = new char[keyData.length()];
204: keyData.getChars(0, pem_chars.length, pem_chars, 0);
205:
206: ps.data = Base64.decode(pem_chars);
207:
208: if (ps.data.length == 0)
209: throw new IOException(
210: "Invalid PEM structure, no data available");
211:
212: return ps;
213: }
214:
215: private static final void decryptPEM(PEMStructure ps, byte[] pw)
216: throws IOException {
217: if (ps.dekInfo == null)
218: throw new IOException(
219: "Broken PEM, no mode and salt given, but encryption enabled");
220:
221: if (ps.dekInfo.length != 2)
222: throw new IOException("Broken PEM, DEK-Info is incomplete!");
223:
224: String algo = ps.dekInfo[0];
225: byte[] salt = hexToByteArray(ps.dekInfo[1]);
226:
227: BlockCipher bc = null;
228:
229: if (algo.equals("DES-EDE3-CBC")) {
230: DESede des3 = new DESede();
231: des3.init(false, generateKeyFromPasswordSaltWithMD5(pw,
232: salt, 24));
233: bc = new CBCMode(des3, salt, false);
234: } else if (algo.equals("DES-CBC")) {
235: DES des = new DES();
236: des.init(false, generateKeyFromPasswordSaltWithMD5(pw,
237: salt, 8));
238: bc = new CBCMode(des, salt, false);
239: } else if (algo.equals("AES-128-CBC")) {
240: AES aes = new AES();
241: aes.init(false, generateKeyFromPasswordSaltWithMD5(pw,
242: salt, 16));
243: bc = new CBCMode(aes, salt, false);
244: } else if (algo.equals("AES-192-CBC")) {
245: AES aes = new AES();
246: aes.init(false, generateKeyFromPasswordSaltWithMD5(pw,
247: salt, 24));
248: bc = new CBCMode(aes, salt, false);
249: } else if (algo.equals("AES-256-CBC")) {
250: AES aes = new AES();
251: aes.init(false, generateKeyFromPasswordSaltWithMD5(pw,
252: salt, 32));
253: bc = new CBCMode(aes, salt, false);
254: } else {
255: throw new IOException(
256: "Cannot decrypt PEM structure, unknown cipher "
257: + algo);
258: }
259:
260: if ((ps.data.length % bc.getBlockSize()) != 0)
261: throw new IOException(
262: "Invalid PEM structure, size of encrypted block is not a multiple of "
263: + bc.getBlockSize());
264:
265: /* Now decrypt the content */
266:
267: byte[] dz = new byte[ps.data.length];
268:
269: for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) {
270: bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i
271: * bc.getBlockSize());
272: }
273:
274: /* Now check and remove RFC 1423/PKCS #7 padding */
275:
276: dz = removePadding(dz, bc.getBlockSize());
277:
278: ps.data = dz;
279: ps.dekInfo = null;
280: ps.procType = null;
281: }
282:
283: public static final boolean isPEMEncrypted(PEMStructure ps)
284: throws IOException {
285: if (ps.procType == null)
286: return false;
287:
288: if (ps.procType.length != 2)
289: throw new IOException("Unknown Proc-Type field.");
290:
291: if ("4".equals(ps.procType[0]) == false)
292: throw new IOException("Unknown Proc-Type field ("
293: + ps.procType[0] + ")");
294:
295: if ("ENCRYPTED".equals(ps.procType[1]))
296: return true;
297:
298: return false;
299: }
300:
301: public static Object decode(char[] pem, String password)
302: throws IOException {
303: PEMStructure ps = parsePEM(pem);
304:
305: if (isPEMEncrypted(ps)) {
306: if (password == null)
307: throw new IOException(
308: "PEM is encrypted, but no password was specified");
309:
310: decryptPEM(ps, password.getBytes());
311: }
312:
313: if (ps.pemType == PEM_DSA_PRIVATE_KEY) {
314: SimpleDERReader dr = new SimpleDERReader(ps.data);
315:
316: byte[] seq = dr.readSequenceAsByteArray();
317:
318: if (dr.available() != 0)
319: throw new IOException(
320: "Padding in DSA PRIVATE KEY DER stream.");
321:
322: dr.resetInput(seq);
323:
324: BigInteger version = dr.readInt();
325:
326: if (version.compareTo(BigInteger.ZERO) != 0)
327: throw new IOException("Wrong version (" + version
328: + ") in DSA PRIVATE KEY DER stream.");
329:
330: BigInteger p = dr.readInt();
331: BigInteger q = dr.readInt();
332: BigInteger g = dr.readInt();
333: BigInteger y = dr.readInt();
334: BigInteger x = dr.readInt();
335:
336: if (dr.available() != 0)
337: throw new IOException(
338: "Padding in DSA PRIVATE KEY DER stream.");
339:
340: return new DSAPrivateKey(p, q, g, y, x);
341: }
342:
343: if (ps.pemType == PEM_RSA_PRIVATE_KEY) {
344: SimpleDERReader dr = new SimpleDERReader(ps.data);
345:
346: byte[] seq = dr.readSequenceAsByteArray();
347:
348: if (dr.available() != 0)
349: throw new IOException(
350: "Padding in RSA PRIVATE KEY DER stream.");
351:
352: dr.resetInput(seq);
353:
354: BigInteger version = dr.readInt();
355:
356: if ((version.compareTo(BigInteger.ZERO) != 0)
357: && (version.compareTo(BigInteger.ONE) != 0))
358: throw new IOException("Wrong version (" + version
359: + ") in RSA PRIVATE KEY DER stream.");
360:
361: BigInteger n = dr.readInt();
362: BigInteger e = dr.readInt();
363: BigInteger d = dr.readInt();
364:
365: return new RSAPrivateKey(d, e, n);
366: }
367:
368: throw new IOException("PEM problem: it is of unknown type");
369: }
370:
371: }
|