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