1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44: import ;
45: import ;
46:
47: import ;
48:
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60: import ;
61:
62: import ;
63: import ;
64: import ;
65: import ;
66:
67: import ;
68: import ;
69: import ;
70: import ;
71: import ;
72:
73: import ;
74: import ;
75: import ;
76: import ;
77: import ;
78: import ;
79:
80: import ;
81: import ;
82:
83:
87: public class PrivateCredentials implements ManagerFactoryParameters
88: {
89:
90:
91:
92:
93: public static final String BEGIN_DSA = "-----BEGIN DSA PRIVATE KEY";
94: public static final String END_DSA = "-----END DSA PRIVATE KEY";
95: public static final String BEGIN_RSA = "-----BEGIN RSA PRIVATE KEY";
96: public static final String END_RSA = "-----END RSA PRIVATE KEY";
97:
98: private List privateKeys;
99: private List certChains;
100:
101:
102:
103:
104: public PrivateCredentials()
105: {
106: privateKeys = new LinkedList();
107: certChains = new LinkedList();
108: }
109:
110:
111:
112:
113: public void add(InputStream certChain, InputStream privateKey)
114: throws CertificateException, InvalidKeyException, InvalidKeySpecException,
115: IOException, NoSuchAlgorithmException, WrongPaddingException
116: {
117: CertificateFactory cf = CertificateFactory.getInstance("X.509");
118: Collection certs = cf.generateCertificates(certChain);
119: X509Certificate[] chain = (X509Certificate[]) certs.toArray(new X509Certificate[0]);
120:
121: String alg = null;
122: String line = readLine(privateKey);
123: String finalLine = null;
124: if (line.startsWith(BEGIN_DSA))
125: {
126: alg = "DSA";
127: finalLine = END_DSA;
128: }
129: else if (line.startsWith(BEGIN_RSA))
130: {
131: alg = "RSA";
132: finalLine = END_RSA;
133: }
134: else
135: throw new IOException("Unknown private key type.");
136:
137: boolean encrypted = false;
138: String cipher = null;
139: String salt = null;
140: StringBuffer base64 = new StringBuffer();
141: while (true)
142: {
143: line = readLine(privateKey);
144: if (line == null)
145: throw new EOFException("premature end-of-file");
146: else if (line.startsWith("Proc-Type: 4,ENCRYPTED"))
147: encrypted = true;
148: else if (line.startsWith("DEK-Info: "))
149: {
150: int i = line.indexOf(',');
151: if (i < 0)
152: cipher = line.substring(10).trim();
153: else
154: {
155: cipher = line.substring(10, i).trim();
156: salt = line.substring(i + 1).trim();
157: }
158: }
159: else if (line.startsWith(finalLine))
160: break;
161: else if (line.length() > 0)
162: {
163: base64.append(line);
164: base64.append(System.getProperty("line.separator"));
165: }
166: }
167:
168: byte[] enckey = Base64.decode(base64.toString());
169: if (encrypted)
170: {
171: enckey = decryptKey(enckey, cipher, toByteArray(salt));
172: }
173:
174: DERReader der = new DERReader(enckey);
175: if (der.read().getTag() != DER.SEQUENCE)
176: throw new IOException("malformed DER sequence");
177: der.read();
178:
179: KeyFactory kf = KeyFactory.getInstance(alg);
180: KeySpec spec = null;
181: if (alg.equals("DSA"))
182: {
183: BigInteger p = (BigInteger) der.read().getValue();
184: BigInteger q = (BigInteger) der.read().getValue();
185: BigInteger g = (BigInteger) der.read().getValue();
186: der.read();
187: BigInteger x = (BigInteger) der.read().getValue();
188: spec = new DSAPrivateKeySpec(x, p, q, g);
189: }
190: else
191: {
192: spec = new RSAPrivateCrtKeySpec(
193: (BigInteger) der.read().getValue(),
194: (BigInteger) der.read().getValue(),
195: (BigInteger) der.read().getValue(),
196: (BigInteger) der.read().getValue(),
197: (BigInteger) der.read().getValue(),
198: (BigInteger) der.read().getValue(),
199: (BigInteger) der.read().getValue(),
200: (BigInteger) der.read().getValue());
201: }
202: privateKeys.add(kf.generatePrivate(spec));
203: certChains.add(chain);
204: }
205:
206: public List getPrivateKeys()
207: {
208: if (isDestroyed())
209: {
210: throw new IllegalStateException("this object is destroyed");
211: }
212: return privateKeys;
213: }
214:
215: public List getCertChains()
216: {
217: return certChains;
218: }
219:
220: public void destroy()
221: {
222: privateKeys.clear();
223: privateKeys = null;
224: }
225:
226: public boolean isDestroyed()
227: {
228: return (privateKeys == null);
229: }
230:
231:
232:
233:
234: private String readLine(InputStream in) throws IOException
235: {
236: boolean eol_is_cr = System.getProperty("line.separator").equals("\r");
237: StringBuffer str = new StringBuffer();
238: while (true)
239: {
240: int i = in.read();
241: if (i == -1)
242: {
243: if (str.length() > 0)
244: break;
245: else
246: return null;
247: }
248: else if (i == '\r')
249: {
250: if (eol_is_cr)
251: break;
252: }
253: else if (i == '\n')
254: break;
255: else
256: str.append((char) i);
257: }
258: return str.toString();
259: }
260:
261: private byte[] decryptKey(byte[] ct, String cipher, byte[] salt)
262: throws IOException, InvalidKeyException, WrongPaddingException
263: {
264: byte[] pt = new byte[ct.length];
265: IMode mode = null;
266: if (cipher.equals("DES-EDE3-CBC"))
267: {
268: mode = ModeFactory.getInstance("CBC", "TripleDES", 8);
269: HashMap attr = new HashMap();
270: attr.put(IMode.KEY_MATERIAL, deriveKey(salt, 24));
271: attr.put(IMode.IV, salt);
272: attr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
273: mode.init(attr);
274: }
275: else if (cipher.equals("DES-CBC"))
276: {
277: mode = ModeFactory.getInstance("CBC", "DES", 8);
278: HashMap attr = new HashMap();
279: attr.put(IMode.KEY_MATERIAL, deriveKey(salt, 8));
280: attr.put(IMode.IV, salt);
281: attr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
282: mode.init(attr);
283: }
284: else
285: throw new IllegalArgumentException("unknown cipher: " + cipher);
286:
287: for (int i = 0; i < ct.length; i += 8)
288: mode.update(ct, i, pt, i);
289:
290: int pad = pt[pt.length-1];
291: if (pad < 1 || pad > 8)
292: throw new WrongPaddingException();
293: for (int i = pt.length - pad; i < pt.length; i++)
294: {
295: if (pt[i] != pad)
296: throw new WrongPaddingException();
297: }
298:
299: byte[] result = new byte[pt.length - pad];
300: System.arraycopy(pt, 0, result, 0, result.length);
301: return result;
302: }
303:
304: private byte[] deriveKey(byte[] salt, int keylen)
305: throws IOException
306: {
307: CallbackHandler passwordHandler = new ConsoleCallbackHandler();
308: try
309: {
310: Class c = Class.forName(Security.getProperty("jessie.password.handler"));
311: passwordHandler = (CallbackHandler) c.newInstance();
312: }
313: catch (Exception x) { }
314:
315: PasswordCallback passwdCallback =
316: new PasswordCallback("Enter PEM passphrase: ", false);
317: try
318: {
319: passwordHandler.handle(new Callback[] { passwdCallback });
320: }
321: catch (UnsupportedCallbackException uce)
322: {
323: throw new IOException("specified handler cannot handle passwords");
324: }
325: char[] passwd = passwdCallback.getPassword();
326:
327: IMessageDigest md5 = HashFactory.getInstance("MD5");
328: byte[] key = new byte[keylen];
329: int count = 0;
330: while (count < keylen)
331: {
332: for (int i = 0; i < passwd.length; i++)
333: md5.update((byte) passwd[i]);
334: md5.update(salt, 0, salt.length);
335: byte[] digest = md5.digest();
336: int len = Math.min(digest.length, keylen - count);
337: System.arraycopy(digest, 0, key, count, len);
338: count += len;
339: if (count >= keylen)
340: break;
341: md5.reset();
342: md5.update(digest, 0, digest.length);
343: }
344: passwdCallback.clearPassword();
345: return key;
346: }
347:
348: private byte[] toByteArray(String hex)
349: {
350: hex = hex.toLowerCase();
351: byte[] buf = new byte[hex.length() / 2];
352: int j = 0;
353: for (int i = 0; i < buf.length; i++)
354: {
355: buf[i] = (byte) ((Character.digit(hex.charAt(j++), 16) << 4) |
356: Character.digit(hex.charAt(j++), 16));
357: }
358: return buf;
359: }
360: }