Source for gnu.javax.net.ssl.PrivateCredentials

   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 java.io.EOFException;
  42: import java.io.InputStream;
  43: import java.io.IOException;
  44: 
  45: import java.math.BigInteger;
  46: 
  47: import java.security.InvalidKeyException;
  48: import java.security.KeyFactory;
  49: import java.security.NoSuchAlgorithmException;
  50: import java.security.PrivateKey;
  51: import java.security.Security;
  52: import java.security.cert.Certificate;
  53: import java.security.cert.CertificateException;
  54: import java.security.cert.CertificateFactory;
  55: import java.security.cert.X509Certificate;
  56: import java.security.spec.DSAPrivateKeySpec;
  57: import java.security.spec.InvalidKeySpecException;
  58: import java.security.spec.KeySpec;
  59: import java.security.spec.RSAPrivateCrtKeySpec;
  60: 
  61: import java.util.Collection;
  62: import java.util.HashMap;
  63: import java.util.LinkedList;
  64: import java.util.List;
  65: 
  66: import javax.net.ssl.ManagerFactoryParameters;
  67: import javax.security.auth.callback.Callback;
  68: import javax.security.auth.callback.CallbackHandler;
  69: import javax.security.auth.callback.PasswordCallback;
  70: import javax.security.auth.callback.UnsupportedCallbackException;
  71: 
  72: import gnu.javax.security.auth.callback.ConsoleCallbackHandler;
  73: import gnu.java.security.hash.HashFactory;
  74: import gnu.java.security.hash.IMessageDigest;
  75: import gnu.javax.crypto.mode.IMode;
  76: import gnu.javax.crypto.mode.ModeFactory;
  77: import gnu.javax.crypto.pad.WrongPaddingException;
  78: 
  79: import gnu.java.security.der.DER;
  80: import gnu.java.security.der.DERReader;
  81: import gnu.java.util.Base64;
  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<PrivateKey> privateKeys;
  99:   private List<X509Certificate[]> certChains;
 100: 
 101:   // Constructor.
 102:   // -------------------------------------------------------------------------
 103: 
 104:   public PrivateCredentials()
 105:   {
 106:     privateKeys = new LinkedList<PrivateKey>();
 107:     certChains = new LinkedList<X509Certificate[]>();
 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<? 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(); // 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: 
 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:   // Own methods.
 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: }