1 /* 2 * Copyright (c) 1998, 2017, 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.InvalidParameterSpecException; 42 import java.security.spec.PKCS8EncodedKeySpec; 43 44 import javax.crypto.Cipher; 45 import javax.crypto.CipherSpi; 46 import javax.crypto.SecretKey; 47 import javax.crypto.IllegalBlockSizeException; 48 import javax.crypto.SealedObject; 49 import javax.crypto.spec.*; 50 import sun.security.x509.AlgorithmId; 51 import sun.security.util.ObjectIdentifier; 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 ITERATION_COUNT = 200000; 80 private static final int SALT_LEN = 20; // the salt length 81 private static final int DIGEST_LEN = 20; 82 83 // the password used for protecting/recovering keys passed through this 84 // key protector 85 private char[] password; 86 87 KeyProtector(char[] password) { 88 if (password == null) { 89 throw new IllegalArgumentException("password can't be null"); 90 } 91 this.password = password; 92 } 93 94 /** 95 * Protects the given cleartext private key, using the password provided at 96 * construction time. 97 */ 98 byte[] protect(PrivateKey key) 99 throws Exception 100 { 101 // create a random salt (8 bytes) 102 byte[] salt = new byte[8]; 103 SunJCE.getRandom().nextBytes(salt); 104 105 // create PBE parameters from salt and iteration count 106 PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT); 107 108 // create PBE key from password 109 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 110 SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 111 pbeKeySpec.clearPassword(); 112 113 // encrypt private key 114 PBEWithMD5AndTripleDESCipher cipher; 115 cipher = new PBEWithMD5AndTripleDESCipher(); 116 cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null); 117 byte[] plain = key.getEncoded(); 118 byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length); 119 120 // wrap encrypted private key in EncryptedPrivateKeyInfo 121 // (as defined in PKCS#8) 122 AlgorithmParameters pbeParams = 123 AlgorithmParameters.getInstance("PBE", SunJCE.getInstance()); 124 pbeParams.init(pbeSpec); 125 126 AlgorithmId encrAlg = new AlgorithmId 127 (new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams); 128 return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded(); 129 } 130 131 /* 132 * Recovers the cleartext version of the given key (in protected format), 133 * using the password provided at construction time. 134 */ 135 Key recover(EncryptedPrivateKeyInfo encrInfo) 136 throws UnrecoverableKeyException, NoSuchAlgorithmException 137 { 138 byte[] plain; 139 140 try { 141 String encrAlg = encrInfo.getAlgorithm().getOID().toString(); 142 if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID) 143 && !encrAlg.equals(KEY_PROTECTOR_OID)) { 144 throw new UnrecoverableKeyException("Unsupported encryption " 145 + "algorithm"); 146 } 147 148 if (encrAlg.equals(KEY_PROTECTOR_OID)) { 149 // JDK 1.2 style recovery 150 plain = recover(encrInfo.getEncryptedData()); 151 } else { 152 byte[] encodedParams = 153 encrInfo.getAlgorithm().getEncodedParams(); 154 155 // parse the PBE parameters into the corresponding spec 156 AlgorithmParameters pbeParams = 157 AlgorithmParameters.getInstance("PBE"); 158 pbeParams.init(encodedParams); 159 PBEParameterSpec pbeSpec = 160 pbeParams.getParameterSpec(PBEParameterSpec.class); 161 if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) { 162 throw new IOException("PBE iteration count too large"); 163 } 164 165 // create PBE key from password 166 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 167 SecretKey sKey = 168 new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 169 pbeKeySpec.clearPassword(); 170 171 // decrypt private key 172 PBEWithMD5AndTripleDESCipher cipher; 173 cipher = new PBEWithMD5AndTripleDESCipher(); 174 cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null); 175 plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0, 176 encrInfo.getEncryptedData().length); 177 } 178 179 // determine the private-key algorithm, and parse private key 180 // using the appropriate key factory 181 String oidName = new AlgorithmId 182 (new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName(); 183 KeyFactory kFac = KeyFactory.getInstance(oidName); 184 return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain)); 185 186 } catch (NoSuchAlgorithmException ex) { 187 // Note: this catch needed to be here because of the 188 // later catch of GeneralSecurityException 189 throw ex; 190 } catch (IOException ioe) { 191 throw new UnrecoverableKeyException(ioe.getMessage()); 192 } catch (GeneralSecurityException gse) { 193 throw new UnrecoverableKeyException(gse.getMessage()); 194 } 195 } 196 197 /* 198 * Recovers the cleartext version of the given key (in protected format), 199 * using the password provided at construction time. This method implements 200 * the recovery algorithm used by Sun's keystore implementation in 201 * JDK 1.2. 202 */ 203 private byte[] recover(byte[] protectedKey) 204 throws UnrecoverableKeyException, NoSuchAlgorithmException 205 { 206 int i, j; 207 byte[] digest; 208 int numRounds; 209 int xorOffset; // offset in xorKey where next digest will be stored 210 int encrKeyLen; // the length of the encrpyted key 211 212 MessageDigest md = MessageDigest.getInstance("SHA"); 213 214 // Get the salt associated with this key (the first SALT_LEN bytes of 215 // <code>protectedKey</code>) 216 byte[] salt = new byte[SALT_LEN]; 217 System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN); 218 219 // Determine the number of digest rounds 220 encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN; 221 numRounds = encrKeyLen / DIGEST_LEN; 222 if ((encrKeyLen % DIGEST_LEN) != 0) 223 numRounds++; 224 225 // Get the encrypted key portion and store it in "encrKey" 226 byte[] encrKey = new byte[encrKeyLen]; 227 System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen); 228 229 // Set up the byte array which will be XORed with "encrKey" 230 byte[] xorKey = new byte[encrKey.length]; 231 232 // Convert password to byte array, so that it can be digested 233 byte[] passwdBytes = new byte[password.length * 2]; 234 for (i=0, j=0; i<password.length; i++) { 235 passwdBytes[j++] = (byte)(password[i] >> 8); 236 passwdBytes[j++] = (byte)password[i]; 237 } 238 239 // Compute the digests, and store them in "xorKey" 240 for (i = 0, xorOffset = 0, digest = salt; 241 i < numRounds; 242 i++, xorOffset += DIGEST_LEN) { 243 md.update(passwdBytes); 244 md.update(digest); 245 digest = md.digest(); 246 md.reset(); 247 // Copy the digest into "xorKey" 248 if (i < numRounds - 1) { 249 System.arraycopy(digest, 0, xorKey, xorOffset, 250 digest.length); 251 } else { 252 System.arraycopy(digest, 0, xorKey, xorOffset, 253 xorKey.length - xorOffset); 254 } 255 } 256 257 // XOR "encrKey" with "xorKey", and store the result in "plainKey" 258 byte[] plainKey = new byte[encrKey.length]; 259 for (i = 0; i < plainKey.length; i++) { 260 plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]); 261 } 262 263 // Check the integrity of the recovered key by concatenating it with 264 // the password, digesting the concatenation, and comparing the 265 // result of the digest operation with the digest provided at the end 266 // of <code>protectedKey</code>. If the two digest values are 267 // different, throw an exception. 268 md.update(passwdBytes); 269 java.util.Arrays.fill(passwdBytes, (byte)0x00); 270 passwdBytes = null; 271 md.update(plainKey); 272 digest = md.digest(); 273 md.reset(); 274 for (i = 0; i < digest.length; i++) { 275 if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) { 276 throw new UnrecoverableKeyException("Cannot recover key"); 277 } 278 } 279 return plainKey; 280 } 281 282 /** 283 * Seals the given cleartext key, using the password provided at 284 * construction time 285 */ 286 SealedObject seal(Key key) 287 throws Exception 288 { 289 // create a random salt (8 bytes) 290 byte[] salt = new byte[8]; 291 SunJCE.getRandom().nextBytes(salt); 292 293 // create PBE parameters from salt and iteration count 294 PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT); 295 296 // create PBE key from password 297 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 298 SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 299 pbeKeySpec.clearPassword(); 300 301 // seal key 302 Cipher cipher; 303 304 PBEWithMD5AndTripleDESCipher cipherSpi; 305 cipherSpi = new PBEWithMD5AndTripleDESCipher(); 306 cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(), 307 "PBEWithMD5AndTripleDES"); 308 cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec); 309 return new SealedObjectForKeyProtector(key, cipher); 310 } 311 312 /** 313 * Unseals the sealed key. 314 */ 315 Key unseal(SealedObject so) 316 throws NoSuchAlgorithmException, UnrecoverableKeyException 317 { 318 try { 319 // create PBE key from password 320 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); 321 SecretKey skey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); 322 pbeKeySpec.clearPassword(); 323 324 SealedObjectForKeyProtector soForKeyProtector = null; 325 if (!(so instanceof SealedObjectForKeyProtector)) { 326 soForKeyProtector = new SealedObjectForKeyProtector(so); 327 } else { 328 soForKeyProtector = (SealedObjectForKeyProtector)so; 329 } 330 AlgorithmParameters params = soForKeyProtector.getParameters(); 331 if (params == null) { 332 throw new UnrecoverableKeyException("Cannot get " + 333 "algorithm parameters"); 334 } 335 PBEParameterSpec pbeSpec; 336 try { 337 pbeSpec = params.getParameterSpec(PBEParameterSpec.class); 338 } catch (InvalidParameterSpecException ipse) { 339 throw new IOException("Invalid PBE algorithm parameters"); 340 } 341 if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) { 342 throw new IOException("PBE iteration count too large"); 343 } 344 PBEWithMD5AndTripleDESCipher cipherSpi; 345 cipherSpi = new PBEWithMD5AndTripleDESCipher(); 346 Cipher cipher = new CipherForKeyProtector(cipherSpi, 347 SunJCE.getInstance(), 348 "PBEWithMD5AndTripleDES"); 349 cipher.init(Cipher.DECRYPT_MODE, skey, params); 350 return (Key)soForKeyProtector.getObject(cipher); 351 } catch (NoSuchAlgorithmException ex) { 352 // Note: this catch needed to be here because of the 353 // later catch of GeneralSecurityException 354 throw ex; 355 } catch (IOException ioe) { 356 throw new UnrecoverableKeyException(ioe.getMessage()); 357 } catch (ClassNotFoundException cnfe) { 358 throw new UnrecoverableKeyException(cnfe.getMessage()); 359 } catch (GeneralSecurityException gse) { 360 throw new UnrecoverableKeyException(gse.getMessage()); 361 } 362 } 363 } 364 365 366 final class CipherForKeyProtector extends javax.crypto.Cipher { 367 /** 368 * Creates a Cipher object. 369 * 370 * @param cipherSpi the delegate 371 * @param provider the provider 372 * @param transformation the transformation 373 */ 374 protected CipherForKeyProtector(CipherSpi cipherSpi, 375 Provider provider, 376 String transformation) { 377 super(cipherSpi, provider, transformation); 378 } 379 }