Source for gnu.javax.net.ssl.PrivateCredentials

   1: /* PrivateCredentials.java -- private key/certificate pairs.
   2:    Copyright (C) 2006  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 java.io.ByteArrayInputStream;
  42: import java.io.ByteArrayOutputStream;
  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.CertificateException;
  55: import java.security.cert.CertificateFactory;
  56: import java.security.cert.X509Certificate;
  57: import java.security.spec.DSAPrivateKeySpec;
  58: import java.security.spec.InvalidKeySpecException;
  59: import java.security.spec.KeySpec;
  60: import java.security.spec.RSAPrivateCrtKeySpec;
  61: 
  62: import java.util.Collection;
  63: import java.util.HashMap;
  64: import java.util.LinkedList;
  65: import java.util.List;
  66: 
  67: import javax.net.ssl.ManagerFactoryParameters;
  68: import javax.security.auth.callback.Callback;
  69: import javax.security.auth.callback.CallbackHandler;
  70: import javax.security.auth.callback.PasswordCallback;
  71: import javax.security.auth.callback.UnsupportedCallbackException;
  72: 
  73: import gnu.javax.security.auth.callback.ConsoleCallbackHandler;
  74: import gnu.java.security.hash.HashFactory;
  75: import gnu.java.security.hash.IMessageDigest;
  76: import gnu.javax.crypto.mode.IMode;
  77: import gnu.javax.crypto.mode.ModeFactory;
  78: import gnu.javax.crypto.pad.WrongPaddingException;
  79: 
  80: import gnu.java.security.der.DER;
  81: import gnu.java.security.der.DERReader;
  82: 
  83: /**
  84:  * An instance of a manager factory parameters for holding a single
  85:  * certificate/private key pair, encoded in PEM format.
  86:  */
  87: public class PrivateCredentials implements ManagerFactoryParameters
  88: {
  89: 
  90:   // Fields.
  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:   // Constructor.
 102:   // -------------------------------------------------------------------------
 103: 
 104:   public PrivateCredentials()
 105:   {
 106:     privateKeys = new LinkedList();
 107:     certChains = new LinkedList();
 108:   }
 109: 
 110:   // Instance methods.
 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(); // version
 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(); // y
 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(),  // modulus
 194:           (BigInteger) der.read().getValue(),  // pub exponent
 195:           (BigInteger) der.read().getValue(),  // priv expenent
 196:           (BigInteger) der.read().getValue(),  // prime p
 197:           (BigInteger) der.read().getValue(),  // prime q
 198:           (BigInteger) der.read().getValue(),  // d mod (p-1)
 199:           (BigInteger) der.read().getValue(),  // d mod (q-1)
 200:           (BigInteger) der.read().getValue()); // coefficient
 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:   // Own methods.
 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: }