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