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