package com.sun.crypto.provider;
import java.io.IOException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.AlgorithmParameters;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.SecretKey;
import javax.crypto.SealedObject;
import javax.crypto.spec.*;
import javax.security.auth.DestroyFailedException;
import sun.security.x509.AlgorithmId;
import sun.security.util.ObjectIdentifier;
import sun.security.util.SecurityProperties;
final class KeyProtector {
private static final String PBE_WITH_MD5_AND_DES3_CBC_OID
= "1.3.6.1.4.1.42.2.19.1";
private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1";
private static final int MAX_ITERATION_COUNT = 5000000;
private static final int MIN_ITERATION_COUNT = 10000;
private static final int DEFAULT_ITERATION_COUNT = 200000;
private static final int SALT_LEN = 20;
private static final int DIGEST_LEN = 20;
private static final int ITERATION_COUNT;
private char[] password;
static {
int iterationCount = DEFAULT_ITERATION_COUNT;
String ic = SecurityProperties.privilegedGetOverridable(
"jdk.jceks.iterationCount");
if (ic != null && !ic.isEmpty()) {
try {
iterationCount = Integer.parseInt(ic);
if (iterationCount < MIN_ITERATION_COUNT ||
iterationCount > MAX_ITERATION_COUNT) {
iterationCount = DEFAULT_ITERATION_COUNT;
}
} catch (NumberFormatException e) {}
}
ITERATION_COUNT = iterationCount;
}
KeyProtector(char[] password) {
if (password == null) {
throw new IllegalArgumentException("password can't be null");
}
this.password = password;
}
byte[] protect(PrivateKey key)
throws Exception
{
byte[] salt = new byte[8];
SunJCE.getRandom().nextBytes(salt);
PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
SecretKey sKey = null;
PBEWithMD5AndTripleDESCipher cipher;
try {
sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
cipher = new PBEWithMD5AndTripleDESCipher();
cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null);
} finally {
pbeKeySpec.clearPassword();
if (sKey != null) sKey.destroy();
}
byte[] plain = key.getEncoded();
byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length);
Arrays.fill(plain, (byte) 0x00);
AlgorithmParameters pbeParams =
AlgorithmParameters.getInstance("PBE", SunJCE.getInstance());
pbeParams.init(pbeSpec);
AlgorithmId encrAlg = new AlgorithmId
(new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams);
return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();
}
Key recover(EncryptedPrivateKeyInfo encrInfo)
throws UnrecoverableKeyException, NoSuchAlgorithmException
{
byte[] plain = null;
SecretKey sKey = null;
try {
String encrAlg = encrInfo.getAlgorithm().getOID().toString();
if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID)
&& !encrAlg.equals(KEY_PROTECTOR_OID)) {
throw new UnrecoverableKeyException("Unsupported encryption "
+ "algorithm");
}
if (encrAlg.equals(KEY_PROTECTOR_OID)) {
plain = recover(encrInfo.getEncryptedData());
} else {
byte[] encodedParams =
encrInfo.getAlgorithm().getEncodedParams();
AlgorithmParameters pbeParams =
AlgorithmParameters.getInstance("PBE");
pbeParams.init(encodedParams);
PBEParameterSpec pbeSpec =
pbeParams.getParameterSpec(PBEParameterSpec.class);
if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) {
throw new IOException("PBE iteration count too large");
}
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
pbeKeySpec.clearPassword();
PBEWithMD5AndTripleDESCipher cipher;
cipher = new PBEWithMD5AndTripleDESCipher();
cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null);
plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0,
encrInfo.getEncryptedData().length);
}
String oidName = new AlgorithmId
(new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName();
KeyFactory kFac = KeyFactory.getInstance(oidName);
return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain));
} catch (NoSuchAlgorithmException ex) {
throw ex;
} catch (IOException ioe) {
throw new UnrecoverableKeyException(ioe.getMessage());
} catch (GeneralSecurityException gse) {
throw new UnrecoverableKeyException(gse.getMessage());
} finally {
if (plain != null) Arrays.fill(plain, (byte) 0x00);
if (sKey != null) {
try {
sKey.destroy();
} catch (DestroyFailedException e) {
}
}
}
}
private byte[] recover(byte[] protectedKey)
throws UnrecoverableKeyException, NoSuchAlgorithmException
{
int i, j;
byte[] digest;
int numRounds;
int xorOffset;
int encrKeyLen;
MessageDigest md = MessageDigest.getInstance("SHA");
byte[] salt = new byte[SALT_LEN];
System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);
encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;
numRounds = encrKeyLen / DIGEST_LEN;
if ((encrKeyLen % DIGEST_LEN) != 0)
numRounds++;
byte[] encrKey = new byte[encrKeyLen];
System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);
byte[] xorKey = new byte[encrKey.length];
byte[] passwdBytes = new byte[password.length * 2];
for (i=0, j=0; i<password.length; i++) {
passwdBytes[j++] = (byte)(password[i] >> 8);
passwdBytes[j++] = (byte)password[i];
}
for (i = 0, xorOffset = 0, digest = salt;
i < numRounds;
i++, xorOffset += DIGEST_LEN) {
md.update(passwdBytes);
md.update(digest);
digest = md.digest();
md.reset();
if (i < numRounds - 1) {
System.arraycopy(digest, 0, xorKey, xorOffset,
digest.length);
} else {
System.arraycopy(digest, 0, xorKey, xorOffset,
xorKey.length - xorOffset);
}
}
byte[] plainKey = new byte[encrKey.length];
for (i = 0; i < plainKey.length; i++) {
plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);
}
md.update(passwdBytes);
Arrays.fill(passwdBytes, (byte)0x00);
passwdBytes = null;
md.update(plainKey);
digest = md.digest();
md.reset();
for (i = 0; i < digest.length; i++) {
if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {
throw new UnrecoverableKeyException("Cannot recover key");
}
}
return plainKey;
}
SealedObject seal(Key key)
throws Exception
{
byte[] salt = new byte[8];
SunJCE.getRandom().nextBytes(salt);
PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
SecretKey sKey = null;
Cipher cipher;
try {
sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
pbeKeySpec.clearPassword();
PBEWithMD5AndTripleDESCipher cipherSpi;
cipherSpi = new PBEWithMD5AndTripleDESCipher();
cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(),
"PBEWithMD5AndTripleDES");
cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
} finally {
if (sKey != null) sKey.destroy();
}
return new SealedObjectForKeyProtector(key, cipher);
}
Key unseal(SealedObject so)
throws NoSuchAlgorithmException, UnrecoverableKeyException {
SecretKey sKey = null;
try {
PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
sKey = new PBEKey(pbeKeySpec,
"PBEWithMD5AndTripleDES", false);
pbeKeySpec.clearPassword();
SealedObjectForKeyProtector soForKeyProtector = null;
if (!(so instanceof SealedObjectForKeyProtector)) {
soForKeyProtector = new SealedObjectForKeyProtector(so);
} else {
soForKeyProtector = (SealedObjectForKeyProtector)so;
}
AlgorithmParameters params = soForKeyProtector.getParameters();
if (params == null) {
throw new UnrecoverableKeyException("Cannot get " +
"algorithm parameters");
}
PBEParameterSpec pbeSpec;
try {
pbeSpec = params.getParameterSpec(PBEParameterSpec.class);
} catch (InvalidParameterSpecException ipse) {
throw new IOException("Invalid PBE algorithm parameters");
}
if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) {