1 /* 2 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.crypto.provider; 27 28 import java.io.IOException; 29 import java.io.Serializable; 30 import java.security.Security; 31 import java.security.Key; 32 import java.security.PrivateKey; 33 import java.security.Provider; 34 import java.security.KeyFactory; 35 import java.security.MessageDigest; 36 import java.security.GeneralSecurityException; 37 import java.security.NoSuchAlgorithmException; 38 import java.security.NoSuchProviderException; 39 import java.security.UnrecoverableKeyException; 40 import java.security.AlgorithmParameters; 41 import java.security.spec.PKCS8EncodedKeySpec; 42 43 import javax.crypto.Cipher; 44 import javax.crypto.CipherSpi; 45 import javax.crypto.SecretKey; 46 import javax.crypto.IllegalBlockSizeException; 47 import javax.crypto.SealedObject; 48 import javax.crypto.spec.*; 49 import sun.security.x509.AlgorithmId; 50 import sun.security.util.ObjectIdentifier; 51 52 /** 53 * This class implements a protection mechanism for private keys. In JCE, we 54 * use a stronger protection mechanism than in the JDK, because we can use 55 * the <code>Cipher</code> class. 56 * Private keys are protected using the JCE mechanism, and are recovered using 57 * either the JDK or JCE mechanism, depending on how the key has been 58 * protected. This allows us to parse Sun's keystore implementation that ships 59 * with JDK 1.2. 60 * 61 * @author Jan Luehe 62 * 63 * 64 * @see JceKeyStore 65 */ 66 67 final class KeyProtector { 68 69 // defined by SunSoft (SKI project) 70 private static final String PBE_WITH_MD5_AND_DES3_CBC_OID 71 = "1.3.6.1.4.1.42.2.19.1"; 72 73 // JavaSoft proprietary key-protection algorithm (used to protect private 74 // keys in the keystore implementation that comes with JDK 1.2) 75 private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1"; 76 77 private static final int SALT_LEN = 20; // the salt length 78 private static final int DIGEST_LEN = 20; 79 80 // the password used for protecting/recovering keys passed through this 81 // key protector 82 private char[] password; 83 84 KeyProtector(char[] password) { 85 if (password == null) { 86 throw new IllegalArgumentException("password can't be null"); 87 } 88 this.password = password; 89 } 90 91 /** 92 * Protects the given cleartext private key, using the password provided at 93 * construction time. 94 */ 95 byte[] protect(PrivateKey key) 96 throws Exception 97 { 98 // create a random salt (8 bytes) 99 byte[] salt = new byte[8]; 100 SunJCE.getRandom().nextBytes(salt); 101 102 // create PBE parameters from salt and iteration count 103 PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20); 104 105 // create PBE key from password 106 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 107 SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 108 pbeKeySpec.clearPassword(); 109 110 // encrypt private key 111 PBEWithMD5AndTripleDESCipher cipher; 112 cipher = new PBEWithMD5AndTripleDESCipher(); 113 cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null); 114 byte[] plain = key.getEncoded(); 115 byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length); 116 117 // wrap encrypted private key in EncryptedPrivateKeyInfo 118 // (as defined in PKCS#8) 119 AlgorithmParameters pbeParams = 120 AlgorithmParameters.getInstance("PBE", SunJCE.getInstance()); 121 pbeParams.init(pbeSpec); 122 123 AlgorithmId encrAlg = new AlgorithmId 124 (new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams); 125 return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded(); 126 } 127 128 /* 129 * Recovers the cleartext version of the given key (in protected format), 130 * using the password provided at construction time. 131 */ 132 Key recover(EncryptedPrivateKeyInfo encrInfo) 133 throws UnrecoverableKeyException, NoSuchAlgorithmException 134 { 135 byte[] plain; 136 137 try { 138 String encrAlg = encrInfo.getAlgorithm().getOID().toString(); 139 if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID) 140 && !encrAlg.equals(KEY_PROTECTOR_OID)) { 141 throw new UnrecoverableKeyException("Unsupported encryption " 142 + "algorithm"); 143 } 144 145 if (encrAlg.equals(KEY_PROTECTOR_OID)) { 146 // JDK 1.2 style recovery 147 plain = recover(encrInfo.getEncryptedData()); 148 } else { 149 byte[] encodedParams = 150 encrInfo.getAlgorithm().getEncodedParams(); 151 152 // parse the PBE parameters into the corresponding spec 153 AlgorithmParameters pbeParams = 154 AlgorithmParameters.getInstance("PBE"); 155 pbeParams.init(encodedParams); 156 PBEParameterSpec pbeSpec = 157 pbeParams.getParameterSpec(PBEParameterSpec.class); 158 159 // create PBE key from password 160 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 161 SecretKey sKey = 162 new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 163 pbeKeySpec.clearPassword(); 164 165 // decrypt private key 166 PBEWithMD5AndTripleDESCipher cipher; 167 cipher = new PBEWithMD5AndTripleDESCipher(); 168 cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null); 169 plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0, 170 encrInfo.getEncryptedData().length); 171 } 172 173 // determine the private-key algorithm, and parse private key 174 // using the appropriate key factory 175 String oidName = new AlgorithmId 176 (new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName(); 177 KeyFactory kFac = KeyFactory.getInstance(oidName); 178 return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain)); 179 180 } catch (NoSuchAlgorithmException ex) { 181 // Note: this catch needed to be here because of the 182 // later catch of GeneralSecurityException 183 throw ex; 184 } catch (IOException ioe) { 185 throw new UnrecoverableKeyException(ioe.getMessage()); 186 } catch (GeneralSecurityException gse) { 187 throw new UnrecoverableKeyException(gse.getMessage()); 188 } 189 } 190 191 /* 192 * Recovers the cleartext version of the given key (in protected format), 193 * using the password provided at construction time. This method implements 194 * the recovery algorithm used by Sun's keystore implementation in 195 * JDK 1.2. 196 */ 197 private byte[] recover(byte[] protectedKey) 198 throws UnrecoverableKeyException, NoSuchAlgorithmException 199 { 200 int i, j; 201 byte[] digest; 202 int numRounds; 203 int xorOffset; // offset in xorKey where next digest will be stored 204 int encrKeyLen; // the length of the encrpyted key 205 206 MessageDigest md = MessageDigest.getInstance("SHA"); 207 208 // Get the salt associated with this key (the first SALT_LEN bytes of 209 // <code>protectedKey</code>) 210 byte[] salt = new byte[SALT_LEN]; 211 System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN); 212 213 // Determine the number of digest rounds 214 encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN; 215 numRounds = encrKeyLen / DIGEST_LEN; 216 if ((encrKeyLen % DIGEST_LEN) != 0) 217 numRounds++; 218 219 // Get the encrypted key portion and store it in "encrKey" 220 byte[] encrKey = new byte[encrKeyLen]; 221 System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen); 222 223 // Set up the byte array which will be XORed with "encrKey" 224 byte[] xorKey = new byte[encrKey.length]; 225 226 // Convert password to byte array, so that it can be digested 227 byte[] passwdBytes = new byte[password.length * 2]; 228 for (i=0, j=0; i<password.length; i++) { 229 passwdBytes[j++] = (byte)(password[i] >> 8); 230 passwdBytes[j++] = (byte)password[i]; 231 } 232 233 // Compute the digests, and store them in "xorKey" 234 for (i = 0, xorOffset = 0, digest = salt; 235 i < numRounds; 236 i++, xorOffset += DIGEST_LEN) { 237 md.update(passwdBytes); 238 md.update(digest); 239 digest = md.digest(); 240 md.reset(); 241 // Copy the digest into "xorKey" 242 if (i < numRounds - 1) { 243 System.arraycopy(digest, 0, xorKey, xorOffset, 244 digest.length); 245 } else { 246 System.arraycopy(digest, 0, xorKey, xorOffset, 247 xorKey.length - xorOffset); 248 } 249 } 250 251 // XOR "encrKey" with "xorKey", and store the result in "plainKey" 252 byte[] plainKey = new byte[encrKey.length]; 253 for (i = 0; i < plainKey.length; i++) { 254 plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]); 255 } 256 257 // Check the integrity of the recovered key by concatenating it with 258 // the password, digesting the concatenation, and comparing the 259 // result of the digest operation with the digest provided at the end 260 // of <code>protectedKey</code>. If the two digest values are 261 // different, throw an exception. 262 md.update(passwdBytes); 263 java.util.Arrays.fill(passwdBytes, (byte)0x00); 264 passwdBytes = null; 265 md.update(plainKey); 266 digest = md.digest(); 267 md.reset(); 268 for (i = 0; i < digest.length; i++) { 269 if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) { 270 throw new UnrecoverableKeyException("Cannot recover key"); 271 } 272 } 273 return plainKey; 274 } 275 276 /** 277 * Seals the given cleartext key, using the password provided at 278 * construction time 279 */ 280 SealedObject seal(Key key) 281 throws Exception 282 { 283 // create a random salt (8 bytes) 284 byte[] salt = new byte[8]; 285 SunJCE.getRandom().nextBytes(salt); 286 287 // create PBE parameters from salt and iteration count 288 PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20); 289 290 // create PBE key from password 291 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 292 SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 293 pbeKeySpec.clearPassword(); 294 295 // seal key 296 Cipher cipher; 297 298 PBEWithMD5AndTripleDESCipher cipherSpi; 299 cipherSpi = new PBEWithMD5AndTripleDESCipher(); 300 cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(), 301 "PBEWithMD5AndTripleDES"); 302 cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec); 303 return new SealedObjectForKeyProtector(key, cipher); 304 } 305 306 /** 307 * Unseals the sealed key. 308 */ 309 Key unseal(SealedObject so) 310 throws NoSuchAlgorithmException, UnrecoverableKeyException 311 { 312 try { 313 // create PBE key from password 314 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 315 SecretKey skey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 316 pbeKeySpec.clearPassword(); 317 318 SealedObjectForKeyProtector soForKeyProtector = null; 319 if (!(so instanceof SealedObjectForKeyProtector)) { 320 soForKeyProtector = new SealedObjectForKeyProtector(so); 321 } else { 322 soForKeyProtector = (SealedObjectForKeyProtector)so; 323 } 324 AlgorithmParameters params = soForKeyProtector.getParameters(); 325 if (params == null) { 326 throw new UnrecoverableKeyException("Cannot get " + 327 "algorithm parameters"); 328 } 329 PBEWithMD5AndTripleDESCipher cipherSpi; 330 cipherSpi = new PBEWithMD5AndTripleDESCipher(); 331 Cipher cipher = new CipherForKeyProtector(cipherSpi, 332 SunJCE.getInstance(), 333 "PBEWithMD5AndTripleDES"); 334 cipher.init(Cipher.DECRYPT_MODE, skey, params); 335 return (Key)soForKeyProtector.getObject(cipher); 336 } catch (NoSuchAlgorithmException ex) { 337 // Note: this catch needed to be here because of the 338 // later catch of GeneralSecurityException 339 throw ex; 340 } catch (IOException ioe) { 341 throw new UnrecoverableKeyException(ioe.getMessage()); 342 } catch (ClassNotFoundException cnfe) { 343 throw new UnrecoverableKeyException(cnfe.getMessage()); 344 } catch (GeneralSecurityException gse) { 345 throw new UnrecoverableKeyException(gse.getMessage()); 346 } 347 } 348 } 349 350 351 final class CipherForKeyProtector extends javax.crypto.Cipher { 352 /** 353 * Creates a Cipher object. 354 * 355 * @param cipherSpi the delegate 356 * @param provider the provider 357 * @param transformation the transformation 358 */ 359 protected CipherForKeyProtector(CipherSpi cipherSpi, 360 Provider provider, 361 String transformation) { 362 super(cipherSpi, provider, transformation); 363 } 364 }