1 /* PrivateCredentials.java -- private key/certificate pairs.
2    Copyright (C) 2006, 2007  Free Software Foundation, Inc.
3 
4 This file is a part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 USA
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version.  */
37 
38 
39 package gnu.javax.net.ssl;
40 
41 import gnu.java.lang.CPStringBuilder;
42 
43 import java.io.EOFException;
44 import java.io.InputStream;
45 import java.io.IOException;
46 
47 import java.math.BigInteger;
48 
49 import java.security.InvalidKeyException;
50 import java.security.KeyFactory;
51 import java.security.NoSuchAlgorithmException;
52 import java.security.PrivateKey;
53 import java.security.Security;
54 import java.security.cert.Certificate;
55 import java.security.cert.CertificateException;
56 import java.security.cert.CertificateFactory;
57 import java.security.cert.X509Certificate;
58 import java.security.spec.DSAPrivateKeySpec;
59 import java.security.spec.InvalidKeySpecException;
60 import java.security.spec.KeySpec;
61 import java.security.spec.RSAPrivateCrtKeySpec;
62 
63 import java.util.Collection;
64 import java.util.HashMap;
65 import java.util.LinkedList;
66 import java.util.List;
67 
68 import javax.net.ssl.ManagerFactoryParameters;
69 import javax.security.auth.callback.Callback;
70 import javax.security.auth.callback.CallbackHandler;
71 import javax.security.auth.callback.PasswordCallback;
72 import javax.security.auth.callback.UnsupportedCallbackException;
73 
74 import gnu.javax.security.auth.callback.ConsoleCallbackHandler;
75 import gnu.java.security.hash.HashFactory;
76 import gnu.java.security.hash.IMessageDigest;
77 import gnu.javax.crypto.mode.IMode;
78 import gnu.javax.crypto.mode.ModeFactory;
79 import gnu.javax.crypto.pad.WrongPaddingException;
80 
81 import gnu.java.security.der.DER;
82 import gnu.java.security.der.DERReader;
83 import gnu.java.util.Base64;
84 
85 /**
86  * An instance of a manager factory parameters for holding a single
87  * certificate/private key pair, encoded in PEM format.
88  */
89 public class PrivateCredentials implements ManagerFactoryParameters
90 {
91 
92   // Fields.
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   // Constructor.
104   // -------------------------------------------------------------------------
105 
PrivateCredentials()106   public PrivateCredentials()
107   {
108     privateKeys = new LinkedList<PrivateKey>();
109     certChains = new LinkedList<X509Certificate[]>();
110   }
111 
112   // Instance methods.
113   // -------------------------------------------------------------------------
114 
add(InputStream certChain, InputStream privateKey)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(); // version
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(); // y
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(),  // modulus
196           (BigInteger) der.read().getValue(),  // pub exponent
197           (BigInteger) der.read().getValue(),  // priv expenent
198           (BigInteger) der.read().getValue(),  // prime p
199           (BigInteger) der.read().getValue(),  // prime q
200           (BigInteger) der.read().getValue(),  // d mod (p-1)
201           (BigInteger) der.read().getValue(),  // d mod (q-1)
202           (BigInteger) der.read().getValue()); // coefficient
203       }
204 
205     privateKeys.add(kf.generatePrivate(spec));
206     certChains.add(chain);
207   }
208 
getPrivateKeys()209   public List<PrivateKey> getPrivateKeys()
210   {
211     if (isDestroyed())
212       {
213         throw new IllegalStateException("this object is destroyed");
214       }
215     return privateKeys;
216   }
217 
getCertChains()218   public List<X509Certificate[]> getCertChains()
219   {
220     return certChains;
221   }
222 
destroy()223   public void destroy()
224   {
225     privateKeys.clear();
226     privateKeys = null;
227   }
228 
isDestroyed()229   public boolean isDestroyed()
230   {
231     return (privateKeys == null);
232   }
233 
234   // Own methods.
235   // -------------------------------------------------------------------------
236 
readLine(InputStream in)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 
decryptKey(byte[] ct, String cipher, byte[] salt)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 
deriveKey(byte[] salt, int keylen)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 
toByteArray(String hex)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 }
364