1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44:
45: import ;
46:
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60:
61: import ;
62: import ;
63: import ;
64: import ;
65:
66: import ;
67: import ;
68: import ;
69: import ;
70: import ;
71:
72: import ;
73: import ;
74: import ;
75: import ;
76: import ;
77: import ;
78:
79: import ;
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<PrivateKey> privateKeys;
99: private List<X509Certificate[]> certChains;
100:
101:
102:
103:
104: public PrivateCredentials()
105: {
106: privateKeys = new LinkedList<PrivateKey>();
107: certChains = new LinkedList<X509Certificate[]>();
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<? extends Certificate> 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:
203: privateKeys.add(kf.generatePrivate(spec));
204: certChains.add(chain);
205: }
206:
207: public List<PrivateKey> getPrivateKeys()
208: {
209: if (isDestroyed())
210: {
211: throw new IllegalStateException("this object is destroyed");
212: }
213: return privateKeys;
214: }
215:
216: public List<X509Certificate[]> getCertChains()
217: {
218: return certChains;
219: }
220:
221: public void destroy()
222: {
223: privateKeys.clear();
224: privateKeys = null;
225: }
226:
227: public boolean isDestroyed()
228: {
229: return (privateKeys == null);
230: }
231:
232:
233:
234:
235: private String readLine(InputStream in) throws IOException
236: {
237: boolean eol_is_cr = System.getProperty("line.separator").equals("\r");
238: StringBuffer str = new StringBuffer();
239: while (true)
240: {
241: int i = in.read();
242: if (i == -1)
243: {
244: if (str.length() > 0)
245: break;
246: else
247: return null;
248: }
249: else if (i == '\r')
250: {
251: if (eol_is_cr)
252: break;
253: }
254: else if (i == '\n')
255: break;
256: else
257: str.append((char) i);
258: }
259: return str.toString();
260: }
261:
262: private byte[] decryptKey(byte[] ct, String cipher, byte[] salt)
263: throws IOException, InvalidKeyException, WrongPaddingException
264: {
265: byte[] pt = new byte[ct.length];
266: IMode mode = null;
267: if (cipher.equals("DES-EDE3-CBC"))
268: {
269: mode = ModeFactory.getInstance("CBC", "TripleDES", 8);
270: HashMap attr = new HashMap();
271: attr.put(IMode.KEY_MATERIAL, deriveKey(salt, 24));
272: attr.put(IMode.IV, salt);
273: attr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
274: mode.init(attr);
275: }
276: else if (cipher.equals("DES-CBC"))
277: {
278: mode = ModeFactory.getInstance("CBC", "DES", 8);
279: HashMap attr = new HashMap();
280: attr.put(IMode.KEY_MATERIAL, deriveKey(salt, 8));
281: attr.put(IMode.IV, salt);
282: attr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
283: mode.init(attr);
284: }
285: else
286: throw new IllegalArgumentException("unknown cipher: " + cipher);
287:
288: for (int i = 0; i < ct.length; i += 8)
289: mode.update(ct, i, pt, i);
290:
291: int pad = pt[pt.length-1];
292: if (pad < 1 || pad > 8)
293: throw new WrongPaddingException();
294: for (int i = pt.length - pad; i < pt.length; i++)
295: {
296: if (pt[i] != pad)
297: throw new WrongPaddingException();
298: }
299:
300: byte[] result = new byte[pt.length - pad];
301: System.arraycopy(pt, 0, result, 0, result.length);
302: return result;
303: }
304:
305: private byte[] deriveKey(byte[] salt, int keylen)
306: throws IOException
307: {
308: CallbackHandler passwordHandler = new ConsoleCallbackHandler();
309: try
310: {
311: Class c = Class.forName(Security.getProperty("jessie.password.handler"));
312: passwordHandler = (CallbackHandler) c.newInstance();
313: }
314: catch (Exception x) { }
315:
316: PasswordCallback passwdCallback =
317: new PasswordCallback("Enter PEM passphrase: ", false);
318: try
319: {
320: passwordHandler.handle(new Callback[] { passwdCallback });
321: }
322: catch (UnsupportedCallbackException uce)
323: {
324: throw new IOException("specified handler cannot handle passwords");
325: }
326: char[] passwd = passwdCallback.getPassword();
327:
328: IMessageDigest md5 = HashFactory.getInstance("MD5");
329: byte[] key = new byte[keylen];
330: int count = 0;
331: while (count < keylen)
332: {
333: for (int i = 0; i < passwd.length; i++)
334: md5.update((byte) passwd[i]);
335: md5.update(salt, 0, salt.length);
336: byte[] digest = md5.digest();
337: int len = Math.min(digest.length, keylen - count);
338: System.arraycopy(digest, 0, key, count, len);
339: count += len;
340: if (count >= keylen)
341: break;
342: md5.reset();
343: md5.update(digest, 0, digest.length);
344: }
345: passwdCallback.clearPassword();
346: return key;
347: }
348:
349: private byte[] toByteArray(String hex)
350: {
351: hex = hex.toLowerCase();
352: byte[] buf = new byte[hex.length() / 2];
353: int j = 0;
354: for (int i = 0; i < buf.length; i++)
355: {
356: buf[i] = (byte) ((Character.digit(hex.charAt(j++), 16) << 4) |
357: Character.digit(hex.charAt(j++), 16));
358: }
359: return buf;
360: }
361: }