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 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: 
 106:   public PrivateCredentials()
 107:   {
 108:     privateKeys = new LinkedList<PrivateKey>();
 109:     certChains = new LinkedList<X509Certificate[]>();
 110:   }
 111: 
 112:   // Instance methods.
 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(); // 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: 
 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:   // Own methods.
 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: }