package kellinwood.security.zipsigner.optional;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
/**
*/
public class KeyStoreFileManager {
static Provider provider = new BouncyCastleProvider();
public static Provider getProvider() { return provider; }
public static void setProvider(Provider provider) {
if (KeyStoreFileManager.provider != null) Security.removeProvider( KeyStoreFileManager.provider.getName());
KeyStoreFileManager.provider = provider;
Security.addProvider( provider);
}
static LoggerInterface logger = LoggerManager.getLogger( KeyStoreFileManager.class.getName());
static {
// Add the spongycastle version of the BC provider so that the implementation classes returned
// from the keystore are all from the spongycastle libs.
Security.addProvider(getProvider());
}
public static KeyStore loadKeyStore( String keystorePath, String encodedPassword)
throws Exception{
char password[] = null;
try {
if (encodedPassword != null) {
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
}
return loadKeyStore(keystorePath, password);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
}
public static KeyStore createKeyStore( String keystorePath, char[] password)
throws Exception
{
KeyStore ks = null;
if (keystorePath.toLowerCase().endsWith(".bks")) {
ks = KeyStore.getInstance("bks", new BouncyCastleProvider());
}
else ks = new JksKeyStore();
ks.load(null, password);
return ks;
}
public static KeyStore loadKeyStore( String keystorePath, char[] password)
throws Exception
{
KeyStore ks = null;
try {
ks = new JksKeyStore();
FileInputStream fis = new FileInputStream( keystorePath);
ks.load( fis, password);
fis.close();
return ks;
} catch (LoadKeystoreException x) {
// This type of exception is thrown when the keystore is a JKS keystore, but the file is malformed
// or the validity/password check failed. In this case don't bother to attempt loading it as a BKS keystore.
throw x;
} catch (Exception x) {
// logger.warning( x.getMessage(), x);
try {
ks = KeyStore.getInstance("bks", getProvider());
FileInputStream fis = new FileInputStream( keystorePath);
ks.load( fis, password);
fis.close();
return ks;
} catch (Exception e) {
throw new RuntimeException("Failed to load keystore: " + e.getMessage(), e);
}
}
}
public static void writeKeyStore( KeyStore ks, String keystorePath, String encodedPassword)
throws Exception
{
char password[] = null;
try {
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
writeKeyStore( ks, keystorePath, password);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
}
public static void writeKeyStore( KeyStore ks, String keystorePath, char[] password)
throws Exception
{
File keystoreFile = new File( keystorePath);
try {
if (keystoreFile.exists()) {
// I've had some trouble saving new verisons of the keystore file in which the file becomes empty/corrupt.
// Saving the new version to a new file and creating a backup of the old version.
File tmpFile = File.createTempFile( keystoreFile.getName(), null, keystoreFile.getParentFile());
FileOutputStream fos = new FileOutputStream( tmpFile);
ks.store(fos, password);
fos.flush();
fos.close();
/* create a backup of the previous version
int i = 1;
File backup = new File( keystorePath + "." + i + ".bak");
while (backup.exists()) {
i += 1;
backup = new File( keystorePath + "." + i + ".bak");
}
renameTo(keystoreFile, backup);
*/
renameTo(tmpFile, keystoreFile);
} else {
FileOutputStream fos = new FileOutputStream( keystorePath);
ks.store(fos, password);
fos.close();
}
} catch (Exception x) {
try {
File logfile = File.createTempFile("zipsigner-error", ".log", keystoreFile.getParentFile());
PrintWriter pw = new PrintWriter(new FileWriter( logfile));
x.printStackTrace( pw);
pw.flush();
pw.close();
} catch (Exception y) {}
throw x;
}
}
static void copyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
if (destFile.exists() && destFile.isDirectory()) {
throw new IOException("Destination '" + destFile + "' exists but is a directory");
}
FileInputStream input = new FileInputStream(srcFile);
try {
FileOutputStream output = new FileOutputStream(destFile);
try {
byte[] buffer = new byte[4096];
long count = 0;
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
} finally {
try { output.close(); } catch (IOException x) {} // Ignore
}
} finally {
try { input.close(); } catch (IOException x) {}
}
if (srcFile.length() != destFile.length()) {
throw new IOException("Failed to copy full contents from '" +
srcFile + "' to '" + destFile + "'");
}
if (preserveFileDate) {
destFile.setLastModified(srcFile.lastModified());
}
}
public static void renameTo(File fromFile, File toFile)
throws IOException
{
copyFile(fromFile, toFile, true);
if (!fromFile.delete()) throw new IOException("Failed to delete " + fromFile);
}
public static void deleteKey(String storePath, String storePass, String keyName)
throws Exception
{
KeyStore ks = loadKeyStore( storePath, storePass);
ks.deleteEntry( keyName);
writeKeyStore(ks, storePath, storePass);
}
public static String renameKey( String keystorePath, String storePass, String oldKeyName, String newKeyName, String keyPass)
throws Exception
{
char[] keyPw = null;
try {
KeyStore ks = loadKeyStore(keystorePath, storePass);
if (ks instanceof JksKeyStore) newKeyName = newKeyName.toLowerCase();
if (ks.containsAlias(newKeyName)) throw new KeyNameConflictException();
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword( keystorePath, oldKeyName, keyPass);
Key key = ks.getKey(oldKeyName, keyPw);
Certificate cert = ks.getCertificate( oldKeyName);
ks.setKeyEntry(newKeyName, key, keyPw, new Certificate[] { cert});
ks.deleteEntry( oldKeyName);
writeKeyStore(ks, keystorePath, storePass);
return newKeyName;
}
finally {
PasswordObfuscator.flush(keyPw);
}
}
public static KeyStore.Entry getKeyEntry( String keystorePath, String storePass, String keyName, String keyPass)
throws Exception
{
char[] keyPw = null;
KeyStore.PasswordProtection passwordProtection = null;
try {
KeyStore ks = loadKeyStore(keystorePath, storePass);
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword( keystorePath, keyName, keyPass);
passwordProtection = new KeyStore.PasswordProtection(keyPw);
return ks.getEntry( keyName, passwordProtection);
}
finally {
if (keyPw != null) PasswordObfuscator.flush(keyPw);
if (passwordProtection != null) passwordProtection.destroy();
}
}
public static boolean containsKey( String keystorePath, String storePass, String keyName)
throws Exception
{
KeyStore ks = loadKeyStore(keystorePath, storePass);
return ks.containsAlias( keyName);
}
/**
*
* @param keystorePath
* @param encodedPassword
* @throws Exception if the password is invalid
*/
public static void validateKeystorePassword( String keystorePath, String encodedPassword)
throws Exception
{
char[] password = null;
try {
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, encodedPassword);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
}
/**
*
* @param keystorePath
* @param keyName
* @param encodedPassword
* @throws java.security.UnrecoverableKeyException if the password is invalid
*/
public static void validateKeyPassword( String keystorePath, String keyName, String encodedPassword)
throws Exception
{
char[] password = null;
try {
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, (char[])null);
password = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath,keyName, encodedPassword);
ks.getKey(keyName, password);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
}
}