1 /* 2 * Copyright (c) 1998, 2018, 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.security.Key; 30 import java.security.PrivateKey; 31 import java.security.Provider; 32 import java.security.KeyFactory; 33 import java.security.MessageDigest; 34 import java.security.GeneralSecurityException; 35 import java.security.NoSuchAlgorithmException; 36 import java.security.UnrecoverableKeyException; 37 import java.security.AlgorithmParameters; 38 import java.security.spec.InvalidParameterSpecException; 39 import java.security.spec.PKCS8EncodedKeySpec; 40 import java.util.Arrays; 41 42 import javax.crypto.Cipher; 43 import javax.crypto.CipherSpi; 44 import javax.crypto.SecretKey; 45 import javax.crypto.SealedObject; 46 import javax.crypto.spec.*; 47 import javax.security.auth.DestroyFailedException; 48 49 import sun.security.x509.AlgorithmId; 50 import sun.security.util.ObjectIdentifier; 51 import sun.security.util.SecurityProperties; 52 53 /** 54 * This class implements a protection mechanism for private keys. In JCE, we 55 * use a stronger protection mechanism than in the JDK, because we can use 56 * the <code>Cipher</code> class. 57 * Private keys are protected using the JCE mechanism, and are recovered using 58 * either the JDK or JCE mechanism, depending on how the key has been 59 * protected. This allows us to parse Sun's keystore implementation that ships 60 * with JDK 1.2. 61 * 62 * @author Jan Luehe 63 * 64 * 65 * @see JceKeyStore 66 */ 67 68 final class KeyProtector { 69 70 // defined by SunSoft (SKI project) 71 private static final String PBE_WITH_MD5_AND_DES3_CBC_OID 72 = "1.3.6.1.4.1.42.2.19.1"; 73 74 // JavaSoft proprietary key-protection algorithm (used to protect private 75 // keys in the keystore implementation that comes with JDK 1.2) 76 private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1"; 77 78 private static final int MAX_ITERATION_COUNT = 5000000; 79 private static final int MIN_ITERATION_COUNT = 10000; 80 private static final int DEFAULT_ITERATION_COUNT = 200000; 81 private static final int SALT_LEN = 20; // the salt length 82 private static final int DIGEST_LEN = 20; 83 private static final int ITERATION_COUNT; 84 85 // the password used for protecting/recovering keys passed through this 86 // key protector 87 private char[] password; 88 89 /** 90 * {@systemProperty jdk.jceks.iterationCount} property indicating the 91 * number of iterations for password-based encryption (PBE) in JCEKS 92 * keystores. Values in the range 10000 to 5000000 are considered valid. 93 * If the value is out of this range, or is not a number, or is 94 * unspecified; a default of 200000 is used. 95 */ 96 static { 97 int iterationCount = DEFAULT_ITERATION_COUNT; 98 String ic = SecurityProperties.privilegedGetOverridable( 99 "jdk.jceks.iterationCount"); 100 if (ic != null && !ic.isEmpty()) { 101 try { 102 iterationCount = Integer.parseInt(ic); 103 if (iterationCount < MIN_ITERATION_COUNT || 104 iterationCount > MAX_ITERATION_COUNT) { 105 iterationCount = DEFAULT_ITERATION_COUNT; 106 } 107 } catch (NumberFormatException e) {} 108 } 109 ITERATION_COUNT = iterationCount; 110 } 111 112 KeyProtector(char[] password) { 113 if (password == null) { 114 throw new IllegalArgumentException("password can't be null"); 115 } 116 this.password = password; 117 } 118 119 /** 120 * Protects the given cleartext private key, using the password provided at 121 * construction time. 122 */ 123 byte[] protect(PrivateKey key) 124 throws Exception 125 { 126 // create a random salt (8 bytes) 127 byte[] salt = new byte[8]; 128 SunJCE.getRandom().nextBytes(salt); 129 130 // create PBE parameters from salt and iteration count 131 PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT); 132 133 // create PBE key from password 134 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 135 SecretKey sKey = null; 136 PBEWithMD5AndTripleDESCipher cipher; 137 try { 138 sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false); 139 // encrypt private key 140 cipher = new PBEWithMD5AndTripleDESCipher(); 141 cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null); 142 } finally { 143 pbeKeySpec.clearPassword(); 144 if (sKey != null) sKey.destroy(); 145 } 146 byte[] plain = key.getEncoded(); 147 byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length); 148 Arrays.fill(plain, (byte) 0x00); 149 150 // wrap encrypted private key in EncryptedPrivateKeyInfo 151 // (as defined in PKCS#8) 152 AlgorithmParameters pbeParams = 153 AlgorithmParameters.getInstance("PBE", SunJCE.getInstance()); 154 pbeParams.init(pbeSpec); 155 156 AlgorithmId encrAlg = new AlgorithmId 157 (new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams); 158 return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded(); 159 } 160 161 /* 162 * Recovers the cleartext version of the given key (in protected format), 163 * using the password provided at construction time. 164 */ 165 Key recover(EncryptedPrivateKeyInfo encrInfo) 166 throws UnrecoverableKeyException, NoSuchAlgorithmException 167 { 168 byte[] plain = null; 169 SecretKey sKey = null; 170 try { 171 String encrAlg = encrInfo.getAlgorithm().getOID().toString(); 172 if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID) 173 && !encrAlg.equals(KEY_PROTECTOR_OID)) { 174 throw new UnrecoverableKeyException("Unsupported encryption " 175 + "algorithm"); 176 } 177 178 if (encrAlg.equals(KEY_PROTECTOR_OID)) { 179 // JDK 1.2 style recovery 180 plain = recover(encrInfo.getEncryptedData()); 181 } else { 182 byte[] encodedParams = 183 encrInfo.getAlgorithm().getEncodedParams(); 184 185 // parse the PBE parameters into the corresponding spec 186 AlgorithmParameters pbeParams = 187 AlgorithmParameters.getInstance("PBE"); 188 pbeParams.init(encodedParams); 189 PBEParameterSpec pbeSpec = 190 pbeParams.getParameterSpec(PBEParameterSpec.class); 191 if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) { 192 throw new IOException("PBE iteration count too large"); 193 } 194 195 // create PBE key from password 196 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 197 sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false); 198 pbeKeySpec.clearPassword(); 199 200 // decrypt private key 201 PBEWithMD5AndTripleDESCipher cipher; 202 cipher = new PBEWithMD5AndTripleDESCipher(); 203 cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null); 204 plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0, 205 encrInfo.getEncryptedData().length); 206 } 207 208 // determine the private-key algorithm, and parse private key 209 // using the appropriate key factory 210 String oidName = new AlgorithmId 211 (new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName(); 212 KeyFactory kFac = KeyFactory.getInstance(oidName); 213 return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain)); 214 } catch (NoSuchAlgorithmException ex) { 215 // Note: this catch needed to be here because of the 216 // later catch of GeneralSecurityException 217 throw ex; 218 } catch (IOException ioe) { 219 throw new UnrecoverableKeyException(ioe.getMessage()); 220 } catch (GeneralSecurityException gse) { 221 throw new UnrecoverableKeyException(gse.getMessage()); 222 } finally { 223 if (plain != null) Arrays.fill(plain, (byte) 0x00); 224 if (sKey != null) { 225 try { 226 sKey.destroy(); 227 } catch (DestroyFailedException e) { 228 //shouldn't happen 229 } 230 } 231 } 232 } 233 234 /* 235 * Recovers the cleartext version of the given key (in protected format), 236 * using the password provided at construction time. This method implements 237 * the recovery algorithm used by Sun's keystore implementation in 238 * JDK 1.2. 239 */ 240 private byte[] recover(byte[] protectedKey) 241 throws UnrecoverableKeyException, NoSuchAlgorithmException 242 { 243 int i, j; 244 byte[] digest; 245 int numRounds; 246 int xorOffset; // offset in xorKey where next digest will be stored 247 int encrKeyLen; // the length of the encrpyted key 248 249 MessageDigest md = MessageDigest.getInstance("SHA"); 250 251 // Get the salt associated with this key (the first SALT_LEN bytes of 252 // <code>protectedKey</code>) 253 byte[] salt = new byte[SALT_LEN]; 254 System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN); 255 256 // Determine the number of digest rounds 257 encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN; 258 numRounds = encrKeyLen / DIGEST_LEN; 259 if ((encrKeyLen % DIGEST_LEN) != 0) 260 numRounds++; 261 262 // Get the encrypted key portion and store it in "encrKey" 263 byte[] encrKey = new byte[encrKeyLen]; 264 System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen); 265 266 // Set up the byte array which will be XORed with "encrKey" 267 byte[] xorKey = new byte[encrKey.length]; 268 269 // Convert password to byte array, so that it can be digested 270 byte[] passwdBytes = new byte[password.length * 2]; 271 for (i=0, j=0; i<password.length; i++) { 272 passwdBytes[j++] = (byte)(password[i] >> 8); 273 passwdBytes[j++] = (byte)password[i]; 274 } 275 276 // Compute the digests, and store them in "xorKey" 277 for (i = 0, xorOffset = 0, digest = salt; 278 i < numRounds; 279 i++, xorOffset += DIGEST_LEN) { 280 md.update(passwdBytes); 281 md.update(digest); 282 digest = md.digest(); 283 md.reset(); 284 // Copy the digest into "xorKey" 285 if (i < numRounds - 1) { 286 System.arraycopy(digest, 0, xorKey, xorOffset, 287 digest.length); 288 } else { 289 System.arraycopy(digest, 0, xorKey, xorOffset, 290 xorKey.length - xorOffset); 291 } 292 } 293 294 // XOR "encrKey" with "xorKey", and store the result in "plainKey" 295 byte[] plainKey = new byte[encrKey.length]; 296 for (i = 0; i < plainKey.length; i++) { 297 plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]); 298 } 299 300 // Check the integrity of the recovered key by concatenating it with 301 // the password, digesting the concatenation, and comparing the 302 // result of the digest operation with the digest provided at the end 303 // of <code>protectedKey</code>. If the two digest values are 304 // different, throw an exception. 305 md.update(passwdBytes); 306 Arrays.fill(passwdBytes, (byte)0x00); 307 passwdBytes = null; 308 md.update(plainKey); 309 digest = md.digest(); 310 md.reset(); 311 for (i = 0; i < digest.length; i++) { 312 if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) { 313 throw new UnrecoverableKeyException("Cannot recover key"); 314 } 315 } 316 return plainKey; 317 } 318 319 /** 320 * Seals the given cleartext key, using the password provided at 321 * construction time 322 */ 323 SealedObject seal(Key key) 324 throws Exception 325 { 326 // create a random salt (8 bytes) 327 byte[] salt = new byte[8]; 328 SunJCE.getRandom().nextBytes(salt); 329 330 // create PBE parameters from salt and iteration count 331 PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT); 332 333 // create PBE key from password 334 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 335 SecretKey sKey = null; 336 Cipher cipher; 337 try { 338 sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false); 339 pbeKeySpec.clearPassword(); 340 341 // seal key 342 PBEWithMD5AndTripleDESCipher cipherSpi; 343 cipherSpi = new PBEWithMD5AndTripleDESCipher(); 344 cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(), 345 "PBEWithMD5AndTripleDES"); 346 cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec); 347 } finally { 348 if (sKey != null) sKey.destroy(); 349 } 350 return new SealedObjectForKeyProtector(key, cipher); 351 } 352 353 /** 354 * Unseals the sealed key. 355 */ 356 Key unseal(SealedObject so) 357 throws NoSuchAlgorithmException, UnrecoverableKeyException { 358 SecretKey sKey = null; 359 try { 360 // create PBE key from password 361 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 362 sKey = new PBEKey(pbeKeySpec, 363 "PBEWithMD5AndTripleDES", false); 364 pbeKeySpec.clearPassword(); 365 366 SealedObjectForKeyProtector soForKeyProtector = null; 367 if (!(so instanceof SealedObjectForKeyProtector)) { 368 soForKeyProtector = new SealedObjectForKeyProtector(so); 369 } else { 370 soForKeyProtector = (SealedObjectForKeyProtector)so; 371 } 372 AlgorithmParameters params = soForKeyProtector.getParameters(); 373 if (params == null) { 374 throw new UnrecoverableKeyException("Cannot get " + 375 "algorithm parameters"); 376 } 377 PBEParameterSpec pbeSpec; 378 try { 379 pbeSpec = params.getParameterSpec(PBEParameterSpec.class); 380 } catch (InvalidParameterSpecException ipse) { 381 throw new IOException("Invalid PBE algorithm parameters"); 382 } 383 if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) { 384 throw new IOException("PBE iteration count too large"); 385 } 386 PBEWithMD5AndTripleDESCipher cipherSpi; 387 cipherSpi = new PBEWithMD5AndTripleDESCipher(); 388 Cipher cipher = new CipherForKeyProtector(cipherSpi, 389 SunJCE.getInstance(), 390 "PBEWithMD5AndTripleDES"); 391 cipher.init(Cipher.DECRYPT_MODE, sKey, params); 392 return soForKeyProtector.getKey(cipher); 393 } catch (NoSuchAlgorithmException ex) { 394 // Note: this catch needed to be here because of the 395 // later catch of GeneralSecurityException 396 throw ex; 397 } catch (IOException ioe) { 398 throw new UnrecoverableKeyException(ioe.getMessage()); 399 } catch (ClassNotFoundException cnfe) { 400 throw new UnrecoverableKeyException(cnfe.getMessage()); 401 } catch (GeneralSecurityException gse) { 402 throw new UnrecoverableKeyException(gse.getMessage()); 403 } finally { 404 if (sKey != null) { 405 try { 406 sKey.destroy(); 407 } catch (DestroyFailedException e) { 408 //shouldn't happen 409 } 410 } 411 } 412 } 413 } 414 415 416 final class CipherForKeyProtector extends javax.crypto.Cipher { 417 /** 418 * Creates a Cipher object. 419 * 420 * @param cipherSpi the delegate 421 * @param provider the provider 422 * @param transformation the transformation 423 */ 424 protected CipherForKeyProtector(CipherSpi cipherSpi, 425 Provider provider, 426 String transformation) { 427 super(cipherSpi, provider, transformation); 428 } 429 }